Compare commits

...

11 Commits

Author SHA1 Message Date
Mike Lang 55f48e1881 cutter: Add unlisted-only safety flag to youtube upload backend 11 months ago
Mike Lang 12150a4005 thrimshim: Return list of available transitions
For use in thrimbletrimmer for a drop-down.
11 months ago
Mike Lang d4de1f94be Add descriptions to xfade transitions 11 months ago
Mike Lang 1dec53924f fix typo 11 months ago
Mike Lang 91511295c0 Fix poster moment tag 11 months ago
Mike Lang 25e5e933b5 Fix typo 11 months ago
Mike Lang 7d89569ead Code more defensively around out-of-order or missing parent ids 11 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.
11 months ago
Mike Lang 23960d947b streamlog: implicit tags must be first 11 months ago
Mike Lang 664f98150f sheets implicit tags fix 11 months ago
Mike Lang 62491d119f debugging 11 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