Compare commits

...

11 Commits

Author SHA1 Message Date
Mike Lang 55f48e1881 cutter: Add unlisted-only safety flag to youtube upload backend 3 months ago
Mike Lang 12150a4005 thrimshim: Return list of available transitions
For use in thrimbletrimmer for a drop-down.
3 months ago
Mike Lang d4de1f94be Add descriptions to xfade transitions 3 months ago
Mike Lang 1dec53924f fix typo 3 months ago
Mike Lang 91511295c0 Fix poster moment tag 3 months ago
Mike Lang 25e5e933b5 Fix typo 3 months ago
Mike Lang 7d89569ead Code more defensively around out-of-order or missing parent ids 3 months ago
Mike Lang 760dbd1e07 sheetsync: Convey row parent info
by prefixing with a number of ^ characters and noting the parent id in the notes column.
3 months ago
Mike Lang 23960d947b streamlog: implicit tags must be first 3 months ago
Mike Lang 664f98150f sheets implicit tags fix 3 months ago
Mike Lang 62491d119f debugging 3 months ago

@ -25,87 +25,93 @@ from .fixts import FixTS
# These are the set of transition names from the ffmpeg xfade filter that we allow. # These are the set of transition names from the ffmpeg xfade filter that we allow.
# This is mainly here to prevent someone putting in arbitrary strings and causing weird problems. # This is mainly here to prevent someone putting in arbitrary strings and causing weird problems,
KNOWN_XFADE_TRANSITIONS = [ # and to provide descriptions.
"fade", # See https://trac.ffmpeg.org/wiki/Xfade for examples.
"wipeleft", KNOWN_XFADE_TRANSITIONS = {
"wiperight", "fade": "A simple cross-fade.",
"wipeup", "fadeblack": "A fade to black then to the new video.",
"wipedown", "fadewhite": "A fade to white then fade to the new video.",
"slideleft", "fadegrays": "The old video fades to grayscale then to the new video.",
"slideright", "wipeleft": "A wipe from right to left.",
"slideup", "wiperight": "A wipe from left to right.",
"slidedown", "wipeup": "A wipe from bottom to top.",
"circlecrop", "wipedown": "A wipe from top to bottom.",
"rectcrop", "slideleft": "The old video slides left as the new video comes in from the right.",
"distance", "slideright": "The old video slides right as the new video comes in from the left.",
"fadeblack", "slideup": "The old video slides up as the new video comes in from the bottom.",
"fadewhite", "slidedown": "The old video slides down as the new video comes in from the top.",
"radial", "circlecrop": "Circular black mask comes in from edges to center, then out again with new video.",
"smoothleft", "rectcrop": "Rectangular black mask comes in from edges to center, then out again with new video.",
"smoothright", "distance": "???",
"smoothup", "radial": "Similar to clock wipe, but with a cross-fading line.",
"smoothdown", "smoothleft": "Similar to wipe left, but with a cross-fading line.",
"circleopen", "smoothright": "Similar to wipe right, but with a cross-fading line.",
"circleclose", "smoothup": "Similar to wipe up, but with a cross-fading line.",
"vertopen", "smoothdown": "Similar to wipe down, but with a cross-fading line.",
"vertclose", "circleopen": "Circular wipe from outside in, with a cross-fading line.",
"horzopen", "circleclose": "Circular wipe from inside out, with a cross-fading line.",
"horzclose", "vertopen": "Wipe from center to either side, with a cross-fading line.",
"dissolve", "vertclose": "Wipe from either side to center, with a cross-fading line.",
"pixelize", "horzopen": "Wipe from center to top and bottom, with a cross-fading line.",
"diagtl", "horzclose": "Wipe from top and bottom to center, with a cross-fading line.",
"diagtr", "dissolve": "Similar to a fade, but each pixel changes instantly, more pixels change over time.",
"diagbl", "pixelize": "Pixelates the image, switches to new video, then unpixelates.",
"diagbr", # TODO the rest later
"hlslice", "diagtl": "",
"hrslice", "diagtr": "",
"vuslice", "diagbl": "",
"vdslice", "diagbr": "",
"hblur", "hlslice": "",
"fadegrays", "hrslice": "",
"wipetl", "vuslice": "",
"wipetr", "vdslice": "",
"wipebl", "hblur": "",
"wipebr", "wipetl": "",
"squeezeh", "wipetr": "",
"squeezev", "wipebl": "",
"zoomin", "wipebr": "",
"fadefast", "squeezeh": "",
"fadeslow", "squeezev": "",
"hlwind", "zoomin": "",
"hrwind", "hlwind": "",
"vuwind", "hrwind": "",
"vdwind", "vuwind": "",
"coverleft", "vdwind": "",
"coverright", "coverleft": "",
"coverup", "coverright": "",
"coverdown", "coverup": "",
"revealleft", "coverdown": "",
"revealright", "revealleft": "",
"revealup", "revealright": "",
"revealdown", "revealup": "",
] "revealdown": "",
"fadefast": "???",
"fadeslow": "???",
}
# These are custom transitions implemented using xfade's custom transition support. # These are custom transitions implemented using xfade's custom transition support.
# It maps from name to the "expr" value to use. # It maps from name to (description, expr).
# In these expressions: # In these expressions:
# X and Y are pixel coordinates # X and Y are pixel coordinates
# A and B are the old and new video's pixel values # A and B are the old and new video's pixel values
# W and H are screen width and height # W and H are screen width and height
# P is a "progress" number from 0 to 1 that increases over the course of the wipe # P is a "progress" number from 0 to 1 that increases over the course of the wipe
CUSTOM_XFADE_TRANSITIONS = { CUSTOM_XFADE_TRANSITIONS = {
# A clock wipe is a 360 degree clockwise sweep around the center of the screen, starting at the top. "clockwipe": (
# It is intended to mimic an analog clock and insinuate a passing of time. "A 360 degree clockwise sweep around the center of the screen, starting at the top.\n"
# It is implemented by calculating the angle of the point off a center line (using atan2()) "Intended to mimic an analog clock and insinuate a passing of time.",
# Implemented by calculating the angle of the point off a center line (using atan2())
# then using the new video if progress > that angle (normalized to 0-1). # then using the new video if progress > that angle (normalized to 0-1).
"clockwipe": "if(lt((1-atan2(W/2-X,Y-H/2)/PI) / 2, P), A, B)", "if(lt((1-atan2(W/2-X,Y-H/2)/PI) / 2, P), A, B)",
# The classic star wipe is an expanding 5-pointed star from the center. ),
# It's mostly a meme. "starwipe": (
# It is implemented by converting to polar coordinates (distance and angle off center), "Wipe using an expanding 5-pointed star from the center. Mostly a meme.",
# Implemented by converting to polar coordinates (distance and angle off center),
# then comparing distance to a star formula derived from here: https://math.stackexchange.com/questions/4293250/how-to-write-a-polar-equation-for-a-five-pointed-star # then comparing distance to a star formula derived from here: https://math.stackexchange.com/questions/4293250/how-to-write-a-polar-equation-for-a-five-pointed-star
# Made by SenseAmidstMadness. # Made by SenseAmidstMadness.
"starwipe": "if(lt(sqrt(pow(X-W/2,2)+pow(Y-H/2,2))/sqrt(pow(W/2,2)+pow(H/2,2)),pow((1-P),2)*(0.75)*1/cos((2*asin(cos(5*(atan2(Y-H/2,X-W/2)+PI/2)))+PI*3)/(10))), B, A)", "if(lt(sqrt(pow(X-W/2,2)+pow(Y-H/2,2))/sqrt(pow(W/2,2)+pow(H/2,2)),pow((1-P),2)*(0.75)*1/cos((2*asin(cos(5*(atan2(Y-H/2,X-W/2)+PI/2)))+PI*3)/(10))), B, A)",
),
} }
@ -505,7 +511,8 @@ def ffmpeg_cut_transition(prev_segments, next_segments, video_type, duration, of
} }
if video_type in CUSTOM_XFADE_TRANSITIONS: if video_type in CUSTOM_XFADE_TRANSITIONS:
xfade_kwargs["transition"] = "custom" xfade_kwargs["transition"] = "custom"
xfade_kwargs["expr"] = f"'{CUSTOM_XFADE_TRANSITIONS[video_type]}'" # wrap in '' for quoting description, expr = CUSTOM_XFADE_TRANSITIONS[video_type]
xfade_kwargs["expr"] = f"'{expr}'" # wrap in '' for quoting
elif video_type in KNOWN_XFADE_TRANSITIONS: elif video_type in KNOWN_XFADE_TRANSITIONS:
xfade_kwargs["transition"] = video_type xfade_kwargs["transition"] = video_type
else: else:

@ -121,6 +121,8 @@ class Youtube(UploadBackend):
to make much difference. to make much difference.
mime_type: You must set this to the correct mime type for the encoded video. mime_type: You must set this to the correct mime type for the encoded video.
Default is video/MP2T, suitable for fast cuts or -f mpegts. Default is video/MP2T, suitable for fast cuts or -f mpegts.
unlisted_only: If set to true, rejects any videos which are set to be public.
Intended as a safety feature when testing.
""" """
needs_transcode = True needs_transcode = True
@ -138,7 +140,7 @@ class Youtube(UploadBackend):
] ]
def __init__(self, credentials, category_id=23, language="en", use_yt_recommended_encoding=False, def __init__(self, credentials, category_id=23, language="en", use_yt_recommended_encoding=False,
mime_type='video/MP2T'): mime_type='video/MP2T', unlisted_only=False):
self.logger = logging.getLogger(type(self).__name__) self.logger = logging.getLogger(type(self).__name__)
self.client = GoogleAPIClient( self.client = GoogleAPIClient(
credentials['client_id'], credentials['client_id'],
@ -148,11 +150,14 @@ class Youtube(UploadBackend):
self.category_id = category_id self.category_id = category_id
self.language = language self.language = language
self.mime_type = mime_type self.mime_type = mime_type
self.unlisted_only = unlisted_only
if use_yt_recommended_encoding: if use_yt_recommended_encoding:
self.encoding_settings = self.recommended_settings self.encoding_settings = self.recommended_settings
self.encoding_streamable = False self.encoding_streamable = False
def upload_video(self, title, description, tags, public, data): def upload_video(self, title, description, tags, public, data):
if public and self.unlisted_only:
raise UploadError("Public video but unlisted-only was requested")
json = { json = {
'snippet': { 'snippet': {
'title': title, 'title': title,

@ -361,7 +361,7 @@ class SheetsEventsMiddleware(SheetsMiddleware):
# Undo the implicitly added tags # Undo the implicitly added tags
if key == "tags": if key == "tags":
value = value[2:] value = value[2:]
if row["poster_moment"]: if row.get("poster_moment"):
value = value[1:] value = value[1:]
return super().write_value(row, key, value) return super().write_value(row, key, value)

@ -126,8 +126,20 @@ class StreamLogEventsMiddleware(Middleware):
def get_rows(self): def get_rows(self):
all_rows = [] all_rows = []
for row in self.client.get_entries()["event_log"]: entries_by_id = {}
row = self.parse_row(row) for entry in self.client.get_entries()["event_log"]:
# Keep track of how many levels of sub-entry each entry is
entries_by_id[entry["id"]] = entry
parent = entry["parent"]
if parent is None:
entry["depth"] = 0
elif parent in entries_by_id:
entry["depth"] = entries_by_id[parent]["depth"] + 1
else:
logging.warning(f"Entry {entry['id']} has unknown or out-of-order parent {parent}")
entry["depth"] = 0
row = self.parse_row(entry)
# Malformed rows can be skipped, represented as a None result # Malformed rows can be skipped, represented as a None result
if row is not None: if row is not None:
all_rows.append(row) all_rows.append(row)
@ -150,12 +162,19 @@ class StreamLogEventsMiddleware(Middleware):
output["sheet_name"] = row["tab"]["name"] if row["tab"] else "unknown" output["sheet_name"] = row["tab"]["name"] if row["tab"] else "unknown"
# Implicit tags # Implicit tags
output['tags'] += [ implicit_tags = [
output['category'], output['category'],
output["sheet_name"], output["sheet_name"],
] ]
if output["poster_moment"]: if output["poster_moment"]:
output['tags'] += 'Poster Moment' implicit_tags.append('Poster Moment')
output['tags'] = implicit_tags + output['tags']
# Convey parent info
if row["depth"] > 0:
output["description"] = "^" * row["depth"] + " " + output["description"]
pre_note = f"Part of event {row['parent']}"
output["notes"] = f"{pre_note}. {output['notes']}" if output["notes"] else pre_note
return output return output

@ -12,12 +12,12 @@ import gevent
import gevent.backdoor import gevent.backdoor
from gevent.pywsgi import WSGIServer from gevent.pywsgi import WSGIServer
import prometheus_client import prometheus_client
import psycopg2
from psycopg2 import sql from psycopg2 import sql
import common import common
from common import database, dateutil from common import database, dateutil
from common.flask_stats import request_stats, after_request from common.flask_stats import request_stats, after_request
from common.segments import KNOWN_XFADE_TRANSITIONS, CUSTOM_XFADE_TRANSITIONS
import google.oauth2.id_token import google.oauth2.id_token
import google.auth.transport.requests import google.auth.transport.requests
@ -144,6 +144,21 @@ def get_defaults():
}) })
@app.route('/thrimshim/transitions')
@request_stats
def get_transitions():
"""Get info on available transitions. Returns a list of {name, description}."""
items = [
(name, description) for name, description in KNOWN_XFADE_TRANSITIONS.items()
] + [
(name, description) for name, (description, expr) in CUSTOM_XFADE_TRANSITIONS.items()
]
return [
{"name": name, "description": description}
for name, description in items
]
@app.route('/thrimshim/<ident>', methods=['GET']) @app.route('/thrimshim/<ident>', methods=['GET'])
@request_stats @request_stats
def get_row(ident): def get_row(ident):

Loading…
Cancel
Save