> ## 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.

# YouTube Shorts vs. Long-Form Performance

> Compare Shorts and long-form video performance using YouTube analytics and Mavera Video Analysis for format strategy recommendations

## 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

```mermaid theme={"dark"}
flowchart LR
    A["YouTube videos.list (filter by duration)"] --> B[Separate Shorts vs long-form] --> C["Mavera POST /video-analysis"] --> D[Compare scores] --> E[Format strategy report]
```

## Code

<CodeGroup>
  ```python Python theme={"dark"}
  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'}")
  ```

  ```javascript JavaScript theme={"dark"}
  const YT = process.env.YOUTUBE_API_KEY;
  const MV = process.env.MAVERA_API_KEY;
  const YT_BASE = "https://www.googleapis.com/youtube/v3";
  const MV_BASE = "https://app.mavera.io/api/v1";
  const MV_H = { Authorization: `Bearer ${MV}`, "Content-Type": "application/json" };

  const CHANNEL_ID = "UC_YOUR_CHANNEL_ID";

  // 1. Uploads playlist
  const channel = await fetch(
    `${YT_BASE}/channels?key=${YT}&id=${CHANNEL_ID}&part=contentDetails`
  ).then(r => r.json());
  const uploadsPlaylist = channel.items[0].contentDetails.relatedPlaylists.uploads;

  // 2. Recent uploads
  const playlistItems = await fetch(
    `${YT_BASE}/playlistItems?key=${YT}&playlistId=${uploadsPlaylist}&part=snippet&maxResults=50`
  ).then(r => r.json());
  const videoIds = (playlistItems.items || []).map(i => i.snippet.resourceId.videoId);

  // 3. Classify
  const details = await fetch(
    `${YT_BASE}/videos?key=${YT}&id=${videoIds.join(",")}&part=snippet,contentDetails,statistics`
  ).then(r => r.json());

  function parseDuration(iso) {
    const m = iso.match(/PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?/);
    if (!m) return 0;
    return (parseInt(m[1] || "0") * 3600) + (parseInt(m[2] || "0") * 60) + parseInt(m[3] || "0");
  }

  const shorts = [], longForm = [];
  for (const v of details.items || []) {
    const dur = parseDuration(v.contentDetails.duration);
    const entry = {
      id: v.id, title: v.snippet.title, duration: dur,
      views: parseInt(v.statistics?.viewCount || "0", 10),
      likes: parseInt(v.statistics?.likeCount || "0", 10),
      comments: parseInt(v.statistics?.commentCount || "0", 10),
    };
    (dur <= 60 ? shorts : longForm).push(entry);
  }

  console.log(`Found ${shorts.length} Shorts, ${longForm.length} long-form`);

  // 4. Analyze top 3 each
  async function analyzeVideos(list, label) {
    const scored = [];
    for (const video of list.sort((a, b) => b.views - a.views).slice(0, 3)) {
      const upload = await fetch(`${MV_BASE}/assets`, {
        method: "POST", headers: MV_H,
        body: JSON.stringify({ url: `https://www.youtube.com/watch?v=${video.id}`, name: `[${label}] ${video.title.slice(0, 40)}`, type: "video" }),
      }).then(r => r.json());

      const analysis = await fetch(`${MV_BASE}/video-analysis`, {
        method: "POST", headers: MV_H,
        body: JSON.stringify({ asset_id: upload.id, analysis_types: ["hook_score", "emotional_arc", "pacing", "cognitive_load", "visual_complexity"] }),
      }).then(r => r.json());

      let status;
      for (let i = 0; i < 30; i++) {
        await new Promise(r => setTimeout(r, 3000));
        status = await fetch(`${MV_BASE}/video-analysis/${analysis.id}`, { headers: MV_H }).then(r => r.json());
        if (status.status === "completed") break;
      }
      const res = status.results || {};
      scored.push({ ...video,
        hook: res.hook_score?.score || 0, emotion: res.emotional_arc?.intensity_avg || 0,
        pacing: res.pacing?.score || 0, cogLoad: res.cognitive_load?.average || 0,
      });
      await new Promise(r => setTimeout(r, 1000));
    }
    return scored;
  }

  const shortsScored = await analyzeVideos(shorts, "Short");
  const longScored = await analyzeVideos(longForm, "Long");

  // 5. Report
  const avg = (arr, key) => arr.length ? arr.reduce((s, v) => s + v[key], 0) / arr.length : 0;

  console.log("\nYOUTUBE SHORTS vs. LONG-FORM COMPARISON");
  console.log("=".repeat(65));
  console.log("Metric                    Shorts (avg)      Long-Form (avg)   Winner");
  console.log("-".repeat(65));

  for (const [label, key, lower] of [
    ["Views", "views", false], ["Hook Score (/100)", "hook", false],
    ["Emotional Intensity (/10)", "emotion", false], ["Pacing (/10)", "pacing", false],
    ["Cognitive Load (/10)", "cogLoad", true],
  ]) {
    const sAvg = avg(shortsScored, key);
    const lAvg = avg(longScored, key);
    const winner = (lower ? sAvg < lAvg : sAvg > lAvg) ? "Shorts" : "Long-Form";
    const fmt = key === "views" ? v => v.toLocaleString() : v => v.toFixed(1);
    console.log(`  ${label.padEnd(25)} ${fmt(sAvg).padEnd(18)} ${fmt(lAvg).padEnd(18)} ${winner}`);
  }
  ```
</CodeGroup>

## Example Output

```text theme={"dark"}
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

<AccordionGroup>
  <Accordion title="Shorts classification heuristic">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.</Accordion>
  <Accordion title="Channel uploads pagination">The `playlistItems` endpoint returns 50 items per page. For channels with 100+ videos, paginate with `nextPageToken`. Each page costs 1 quota unit.</Accordion>
  <Accordion title="Engagement rate calculation">Engagement rate = likes / views. YouTube hides dislike counts (since 2021), so this metric is positivity-biased. Comment count is an alternative engagement signal.</Accordion>
</AccordionGroup>

***

<CardGroup cols={2}>
  <Card title="YouTube Integration" icon="arrow-left" href="/integrations/youtube" />

  <Card title="Video Analysis" icon="video" href="/api-reference/video-analysis" />
</CardGroup>
