update to the process_uploads upload.py and new database table called data

This commit is contained in:
SumGuyV5 committed 2026-09-16 01:11:38 +00:00
1 parent afe6490d20
commit 2773200a94
17 files changed
+245 -70

No files matched your search

+13 -3
View File
@@ -14,22 +14,32 @@ os.makedirs(CACHE_DIR, exist_ok=True)
# 3. Expose Database Tools (Added)
# Replace 'DatabaseClient' and 'connect_to_db' with your actual class/function names
from .build_shorts import fit_to_9_16_letterbox
from .build_videos import build_videos
from .config import CHANNEL_NAME, DB
from .config import CHANNEL_NAME, TZ, DB
from .database import Database
from .hash_tags import get_top_nouns
from .process_uploads import process_uploads
from .transcribe_video import transcribe_to_srt
from .utils import run_command
from .utils import write_csv, run_command
# 4. Expose Subpackages
from . import twitch
from . import youtube
# (Optional) Defines what is exposed during 'from package import *'
__all__ = [
"fit_to_9_16_letterbox"
"build_videos",
"CHANNEL_NAME",
"TZ",
"DB",
"Database",
"get_top_nouns",
"process_uploads",
"transcribe_to_srt",
"utils",
"write_csv",
"run_command",
"twitch",
"youtube"
]
@@ -4,12 +4,9 @@ import json
import package.utils as utils
import tempfile
from pathlib import Path
from moviepy import VideoFileClip, ColorClip, CompositeVideoClip
from moviepy.video.VideoClip import TextClip
from pathlib import Path
import numpy as np
from PIL import Image, ImageFilter
def get_video_info(input_path: Path) -> tuple:
"""Uses ffprobe to instantly read input video dimensions and frame rate."""
+12 -2
View File
@@ -1,8 +1,11 @@
#!/usr/bin/env python3
import package.youtube.shorts as yt_shorts
from datetime import datetime
import package.build_shorts as yt_shorts
import package.transcribe_video as transcribe_video
import package.twitch.chat_vod as chat_vod
from package.config import DB as DB
from package.config import TZ as TZ
def build_chat_video():
chats = DB.get_unuploaded_chats()
@@ -81,6 +84,7 @@ def build_shorts():
shorts = DB.get_unuploaded_shorts()
top_txt = "TeamPGP Live on Twitch...\n Every Friday and Sunday @7:30 ET.\n twitch.tv/teampgp"
count = 1
for short in shorts:
(
@@ -107,8 +111,14 @@ def build_shorts():
print("====================================================")
print(f"🚀 Processing: Clip to Youtube Short {title}")
print("====================================================")
print(f"{count}/{len(shorts)}")
yt_shorts.fit_to_9_16_letterbox(target_dir, top_txt, f"Clipped By: {creator_name}.")
created_at = datetime.strptime(created_at, "%Y-%m-%d %H:%M:%S%z")
created_at = created_at.astimezone(TZ)
#ToDo
yt_shorts.fit_to_9_16_letterbox(target_dir, top_txt, f"Clipped By: {creator_name},\n on {created_at.strftime("%Y-%m-%d %H:%M:%S %Z")}", True, True)
count += 1
print("====================================================")
print(f"✅ Processing: Clip to Youtube Short Done.")
+3
View File
@@ -1,6 +1,9 @@
#!/usr/bin/env python3
from zoneinfo import ZoneInfo
import package.database as database
CHANNEL_NAME = "teampgp"
TZ = ZoneInfo("America/Toronto")
DB = database.Database()
+63
View File
@@ -7,6 +7,7 @@ from typing import Any
class Database:
def __init__(self, db_path: str = "database.db"):
self.columns = "id, title, created_at, view_count, duration, url, thumbnail_url, game_id, game_name, stream_id, creator_name, clip_is, downloaded, uploaded_yt, uploaded_yt_chats, uploaded_yt_shorts"
self.data = "id, name, date_shorts, date_videos, date_videos_chat"
file_exists = Path(db_path).is_file()
@@ -45,6 +46,16 @@ class Database:
uploaded_yt_shorts BOOLEAN NOT NULL DEFAULT 0
)
""")
self.cursor.execute("""
CREATE TABLE IF NOT EXISTS data (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
date_shorts TEXT NOT NULL,
date_videos TEXT NOT NULL,
date_videos_chat TEXT NOT NULL
)
""")
self.conn.commit()
@@ -167,3 +178,55 @@ class Database:
# Prevent silent failures if the database connection drops
print(f"❌ Database insertion failed: {e}")
self.conn.rollback()
def __update_date(self, record_id: str, set_row: str, date: str):
"""Updates the Datetime for timezone fix"""
self.cursor.execute(
f"UPDATE data SET {set_row} = ? WHERE id = ?",
(date, record_id))
self.conn.commit()
def update_date_shorts(self, record_id: str, date_shorts: str):
self.__update_date(record_id, "date_shorts", date_shorts)
def get_data(self) -> list[Any]:
self.cursor.execute(f"SELECT {self.data} FROM data")
return self.cursor.fetchall()
def insert_data_record(self, name: str, date_shorts: str, date_videos: str, date_videos_chat: str):
try:
dt_obj = datetime.strptime(date_shorts, "%Y-%m-%dT%H:%M:%SZ")
clean_date_shorts = dt_obj.strftime("%Y-%m-%d %H:%M:%S")
except (ValueError, TypeError):
clean_date_shorts = date_shorts
try:
dt_obj = datetime.strptime(date_videos, "%Y-%m-%dT%H:%M:%SZ")
clean_date_videos = dt_obj.strftime("%Y-%m-%d %H:%M:%S")
except (ValueError, TypeError):
clean_date_videos = date_videos
try:
dt_obj = datetime.strptime(date_videos_chat, "%Y-%m-%dT%H:%M:%SZ")
clean_date_videos_chat = dt_obj.strftime("%Y-%m-%d %H:%M:%S")
except (ValueError, TypeError):
clean_date_videos_chat = date_videos_chat
# Explicitly defining columns removes the security risk and column-count bug
query = """
INSERT INTO data (
name, date_shorts, date_videos, date_videos_chat
) VALUES (?, ?, ?, ?)
"""
values = (
name, clean_date_shorts, clean_date_videos, clean_date_videos_chat
)
try:
self.cursor.execute(query, values)
self.conn.commit()
except Exception as e:
# Prevent silent failures if the database connection drops
print(f"❌ Database insertion failed: {e}")
self.conn.rollback()
File renamed without changes.
+83
View File
@@ -0,0 +1,83 @@
#!/usr/bin/env python3
import os
from datetime import datetime, timedelta
import package.utils as utils
from package.config import TZ as TZ
from package.config import DB as DB
from package.hash_tags import get_top_nouns as get_top_nouns
from package.youtube.uploader import upload_video as upload_video
from package.youtube.uploader import CategoryId as CategoryId
def process_shorts():
shorts = DB.get_unuploaded_shorts()
data = DB.get_data()
( data_id, name, release_time, _, _) = data[0]
current_time = datetime.now(TZ)
release_time = datetime.strptime(release_time, "%Y-%m-%d %H:%M:%S%z")
if current_time > release_time:
pass
else:
release_time = current_time
release_time = release_time.replace(hour=12, minute=0, second=0, microsecond=0)
for short in shorts:
(
id,
title,
created_at,
view_count,
duration,
url,
thumbnail_url,
game_id,
game_name,
stream_id,
creator_name,
clip_is,
downloaded,
uploaded_yt,
uploaded_yt_chats,
uploaded_yt_shorts,
) = short
original_path = f"download/clips/{id}/{id}.mp4"
file_path = f"download/clips/{id}/{id}_gaussian_9_16.mp4"
file_csv = f"download/clips/{id}/{id}_{name}.csv"
if not os.path.exists(file_path):
file_path = f"download/clips/{id}/{id}_black_9_16.mp4"
if not os.path.exists(file_path):
continue
title = title
created_at = datetime.strptime(created_at, "%Y-%m-%d %H:%M:%S%z")
created_at = created_at.astimezone(TZ)
categoryId = CategoryId.GAMING
description = f"Game: {game_name}, Clipped By: {creator_name}, on {created_at.strftime("%Y-%m-%d %H:%M:%S %Z (%:z)")}, #Shorts #Clips #Twitch Every Friday and Sunday @7:30 ET https://twitch.tv/teampgp"
privatcyStatus = 'private'
tags = ['shorts', 'gaming', 'TeamPGP', f'{game_name}', f'{creator_name}', 'twitch_clips', 'clips', 'Level1Techs', 'twitch']
tags.extend(get_top_nouns(f"download/clips/{id}/{id}.srt"))
data_csv = []
if os.path.exists(original_path):
if video_id := upload_video(original_path, title, categoryId, description, 'unlisted', tags):
data_csv.extend(video_id, current_time)
release_time += timedelta(days=1)
if video_id := upload_video(file_path, title, categoryId, f"{description} Original Full size @ https://youtu.be/{video_id}", privatcyStatus, tags, release_time):
data_csv.extend(video_id, release_time)
DB.mark_as_uploaded_shorts(id)
utils.write_csv(data_csv, file_csv)
DB.update_date_shorts(data_id, release_time)
def process_uploads():
process_shorts()
if __name__ == "__main__":
pass
+13 -12
View File
@@ -12,17 +12,6 @@ os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"
# 5 minutes per chunk (300 seconds) keeps RAM usage low and stable
CHUNK_DURATION_SEC = 300
print("🔧 Initializing C++ Engine...")
model = WhisperModel(
"base",
device="cpu",
compute_type="int8",
cpu_threads=0, # Let CTranslate2 auto-detect safe core counts
num_workers=1
)
print("✅ C++ Model loaded successfully.")
def extract_audio_and_chunk(video_path: str, output_dir: Path) -> list:
"""Extracts and splits audio into 5-minute chunks using a single FFmpeg pass."""
print("🚀 Extracting and chunking audio with FFmpeg...")
@@ -49,6 +38,18 @@ def extract_audio_and_chunk(video_path: str, output_dir: Path) -> list:
def transcribe_to_srt(video_path: str, force: bool = False):
if not hasattr(transcribe_to_srt, "model"):
print("🔧 Initializing C++ Engine...")
transcribe_to_srt.model = WhisperModel(
"base",
device="cpu",
compute_type="int8",
cpu_threads=0, # Let CTranslate2 auto-detect safe core counts
num_workers=1
)
print("✅ C++ Model loaded successfully.")
video_path_obj = Path(video_path)
srt_path = video_path_obj.with_suffix(".srt")
temp_dir = video_path_obj.parent / f"temp_chunks_{video_path_obj.stem}"
@@ -78,7 +79,7 @@ def transcribe_to_srt(video_path: str, force: bool = False):
time_offset = chunk_idx * CHUNK_DURATION_SEC
print(f"\n⏳ Processing chunk {chunk_idx + 1}/{len(audio_chunks)} ({chunk_path.name})...")
segments_generator, info = model.transcribe(
segments_generator, info = transcribe_to_srt.model.transcribe(
str(chunk_path),
beam_size=1,
vad_filter=True,
+1 -10
View File
@@ -1,19 +1,10 @@
#!/usr/bin/env python3
import csv
import subprocess
from datetime import datetime, timedelta, timezone
import package.utils as utils
from package.config import DB as DB
def write_csv(data, file_name):
# Open file with newline='' to prevent extra blank rows across platforms
with open(file_name, "w", newline="", encoding="utf-8") as file:
writer = csv.writer(file)
# Write all rows at once
writer.writerows(data)
def download_videos():
"""Find all undownloaded videos and clips, and download them safely."""
undownloaded = DB.get_undownloaded()
@@ -80,7 +71,7 @@ def download_videos():
if output:
# Only write CSV and update database if download actually completed
write_csv(data, csv_file)
utils.write_csv(data, csv_file)
print(f"✅ Success: TwitchDownloaderCLI video. {target_dir}")
DB.mark_as_downloaded(id)
else:
+9
View File
@@ -1,8 +1,17 @@
#!/usr/bin/env python3
import csv
import sys
import shlex
import subprocess
def write_csv(data, file_name):
# Open file with newline='' to prevent extra blank rows across platforms
with open(file_name, "w", newline="", encoding="utf-8") as file:
writer = csv.writer(file)
# Write all rows at once
writer.writerows(data)
def run_command(cmd_str: str, progress_prefix: str = "Progress", look_for: list = ["frame=", "time=", "fps=", "Rendering frame"]) -> bool:
"""Runs a system command, streams its output live, and reports errors on failure."""
args = shlex.split(cmd_str)
-3
View File
@@ -2,14 +2,11 @@
# Expose components from individual modules using relative imports
from .find_tags import find_tags
from .hashtags import get_top_nouns
from .shorts import fit_to_9_16_letterbox
from .uploader import upload_video, CategoryId
# Explicitly define what is exposed when importing the subpackage
__all__ = [
"find_tags",
"get_top_nouns",
"fit_to_9_16_letterbox",
"upload_video",
]
+10 -8
View File
@@ -2,7 +2,7 @@
import os
import argparse
from enum import Enum
from datetime import datetime
from datetime import datetime, timezone
from googleapiclient.discovery import build
from googleapiclient.http import MediaFileUpload
@@ -29,7 +29,7 @@ class CategoryId(Enum):
SCIENCE_AND_TECHNOLOGY = "28"
NONPROFITS_AND_ACTIVISM = "29"
def upload_video(file_path: str, title: str, category: CategoryId, description: str = "", privacyStatus: str = 'private', tags: list = None, release_time: datetime = None) -> bool:
def upload_video(file_path: str, title: str, category: CategoryId, description: str = "", privacyStatus: str = 'private', tags: list = None, release_time: datetime = None) -> str | None:
"""
Upload a video to YouTube
@@ -51,11 +51,12 @@ def upload_video(file_path: str, title: str, category: CategoryId, description:
# Check if file exists
if not os.path.exists(file_path):
print(f"❌ Error: File not found: {file_path}")
return False
return None
# Load credentials
if not config.CREDENTIALS:
return False
print("❌ Error: YouTube API credentials not configured.")
return None
# Create YouTube API client
youtube = build('youtube', 'v3', credentials=config.CREDENTIALS)
@@ -71,7 +72,8 @@ def upload_video(file_path: str, title: str, category: CategoryId, description:
# 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')
#status_body['publishAt'] = release_time.strftime('%Y-%m-%dT%H:%M:%S.000Z')
status_body['publishAt'] = release_time.isoformat()
print(f"Configuring timed release for: {status_body['publishAt']}")
else:
# Standard immediate upload
@@ -121,14 +123,14 @@ def upload_video(file_path: str, title: str, category: CategoryId, description:
else:
print(f"Current Privacy Status: {response['status']['privacyStatus']}")
return True
return response['id']
except HttpError as e:
print(f"An HTTP error occurred: {str(e)}")
return False
return None
except Exception as e:
print(f"An error occurred: {str(e)}")
return False
return None
def main():
parser = argparse.ArgumentParser(description='Upload a video to YouTube')