Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.mavera.io/llms.txt

Use this file to discover all available pages before exploring further.

Scenario

Should your brand invest in Shorts or long-form? This job pulls your channel’s recent Shorts (under 60 seconds) and long-form videos separately via videos.list, runs Video Analysis on a sample from each format, and compares behavioral scores head-to-head. The result is a format strategy recommendation backed by both YouTube analytics and Mavera’s creative intelligence — showing not just which format gets more views, but which format produces better creative quality for your brand.

Architecture

Code

import os, requests, time, re

YT = os.environ["YOUTUBE_API_KEY"]
MV = os.environ["MAVERA_API_KEY"]
YT_BASE = "https://www.googleapis.com/youtube/v3"
MV_BASE = "https://app.mavera.io/api/v1"
MV_H = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}

CHANNEL_ID = "UC_YOUR_CHANNEL_ID"

# 1. Get channel uploads playlist
channel = requests.get(f"{YT_BASE}/channels", params={
    "key": YT, "id": CHANNEL_ID,
    "part": "contentDetails",
}).json()
uploads_playlist = channel["items"][0]["contentDetails"]["relatedPlaylists"]["uploads"]

# 2. Get recent uploads (1 quota unit per page)
playlist_items = requests.get(f"{YT_BASE}/playlistItems", params={
    "key": YT, "playlistId": uploads_playlist,
    "part": "snippet", "maxResults": 50,
}).json()

video_ids = [item["snippet"]["resourceId"]["videoId"]
             for item in playlist_items.get("items", [])]

# 3. Get full details to classify Shorts vs long-form (1 quota unit)
details = requests.get(f"{YT_BASE}/videos", params={
    "key": YT, "id": ",".join(video_ids),
    "part": "snippet,contentDetails,statistics",
}).json()

def parse_duration(iso):
    match = re.match(r"PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?", iso)
    if not match:
        return 0
    h, m, s = (int(g) if g else 0 for g in match.groups())
    return h * 3600 + m * 60 + s

shorts = []
long_form = []

for video in details.get("items", []):
    duration = parse_duration(video["contentDetails"]["duration"])
    stats = video.get("statistics", {})
    entry = {
        "id": video["id"],
        "title": video["snippet"]["title"],
        "duration": duration,
        "views": int(stats.get("viewCount", 0)),
        "likes": int(stats.get("likeCount", 0)),
        "comments": int(stats.get("commentCount", 0)),
    }
    if duration <= 60:
        shorts.append(entry)
    else:
        long_form.append(entry)

print(f"Found {len(shorts)} Shorts, {len(long_form)} long-form videos")

# 4. Analyze top 3 of each format
def analyze_videos(video_list, label):
    scored = []
    for video in sorted(video_list, key=lambda v: -v["views"])[:3]:
        upload = requests.post(f"{MV_BASE}/assets", headers=MV_H, json={
            "url": f"https://www.youtube.com/watch?v={video['id']}",
            "name": f"[{label}] {video['title'][:40]}", "type": "video",
        }).json()

        analysis = requests.post(f"{MV_BASE}/video-analysis", headers=MV_H, json={
            "asset_id": upload["id"],
            "analysis_types": ["hook_score", "emotional_arc", "pacing", "cognitive_load", "visual_complexity"],
        }).json()

        for _ in range(30):
            time.sleep(3)
            status = requests.get(
                f"{MV_BASE}/video-analysis/{analysis['id']}", headers=MV_H
            ).json()
            if status.get("status") == "completed":
                break

        r = status.get("results", {})
        scored.append({
            **video,
            "hook": r.get("hook_score", {}).get("score", 0),
            "emotion": r.get("emotional_arc", {}).get("intensity_avg", 0),
            "pacing": r.get("pacing", {}).get("score", 0),
            "cog_load": r.get("cognitive_load", {}).get("average", 0),
        })
        time.sleep(1)
    return scored

shorts_scored = analyze_videos(shorts, "Short")
long_scored = analyze_videos(long_form, "Long")

# 5. Comparison report
def avg_metric(lst, key):
    vals = [v[key] for v in lst]
    return sum(vals) / len(vals) if vals else 0

print("\nYOUTUBE SHORTS vs. LONG-FORM COMPARISON")
print("=" * 65)
print(f"{'Metric':<25} {'Shorts (avg)':<18} {'Long-Form (avg)':<18} {'Winner'}")
print("-" * 65)

metrics = [
    ("Views", "views", False),
    ("Hook Score (/100)", "hook", False),
    ("Emotional Intensity (/10)", "emotion", False),
    ("Pacing (/10)", "pacing", False),
    ("Cognitive Load (/10)", "cog_load", True),
]

for label, key, lower_is_better in metrics:
    s_avg = avg_metric(shorts_scored, key)
    l_avg = avg_metric(long_scored, key)
    winner = "Shorts" if (s_avg < l_avg if lower_is_better else s_avg > l_avg) else "Long-Form"
    fmt = ",.0f" if key == "views" else ".1f"
    print(f"  {label:<25} {s_avg:{fmt}:<18} {l_avg:{fmt}:<18} {winner}")

engagement_short = avg_metric(shorts_scored, "likes") / max(avg_metric(shorts_scored, "views"), 1) * 100
engagement_long = avg_metric(long_scored, "likes") / max(avg_metric(long_scored, "views"), 1) * 100
print(f"  {'Engagement Rate':<25} {engagement_short:.2f}%{'':<13} {engagement_long:.2f}%{'':<13} {'Shorts' if engagement_short > engagement_long else 'Long-Form'}")

Example Output

Found 18 Shorts, 32 long-form videos

YOUTUBE SHORTS vs. LONG-FORM COMPARISON
=================================================================
Metric                    Shorts (avg)      Long-Form (avg)   Winner
-----------------------------------------------------------------
  Views                     124,000           45,200            Shorts
  Hook Score (/100)         82                61                Shorts
  Emotional Intensity (/10) 7.2               6.8              Shorts
  Pacing (/10)              9.1               6.4              Shorts
  Cognitive Load (/10)      3.2               5.8              Shorts
  Engagement Rate           4.80%             2.10%            Shorts

INSIGHT: Shorts dominate on reach, hook, and pacing — but engagement
rate (likes/views) is 2.3x higher. Long-form drives deeper engagement
per viewer but reaches fewer people. Recommendation: Use Shorts for
top-of-funnel awareness and long-form for mid-funnel education.
Repurpose long-form highlights as Shorts for maximum coverage.

Error Handling

YouTube’s API doesn’t have a dedicated Shorts flag. Videos under 60 seconds are classified as Shorts. This may include non-Shorts short clips — filter by aspect ratio (9:16) if precision matters.
The playlistItems endpoint returns 50 items per page. For channels with 100+ videos, paginate with nextPageToken. Each page costs 1 quota unit.
Engagement rate = likes / views. YouTube hides dislike counts (since 2021), so this metric is positivity-biased. Comment count is an alternative engagement signal.

YouTube Integration

Video Analysis