npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@aphrody/ietv

v0.2.2

Published

**Inazuma Eleven TV (IETV)** — YouTube channel scraper for Inazuma Eleven French streaming

Readme

@aphrody/ietv

Inazuma Eleven TV (IETV) — YouTube channel scraper for Inazuma Eleven French streaming

Dedicated scraper for aggregating episodes across four official Inazuma Eleven French YouTube channels, resolving episodes by season and episode number.

Installation

npm install @aphrody/ietv
# or
bun install @aphrody/ietv

Usage

Programmatic API

import IETVScraper from "@aphrody/ietv";

const scraper = new IETVScraper({ profile: "fast" });

// Get episodes from a single channel
const channelInfo = await scraper.getChannelEpisodes("inazumaelevenfrance1");
console.log(`${channelInfo.channel}: ${channelInfo.totalEpisodes} episodes`);

// Aggregate all 4 channels
const allChannels = await scraper.getAllChannelEpisodes();
for (const ch of allChannels) {
  console.log(`${ch.title}: ${ch.seasons.length} seasons`);
}

await scraper.close();

CLI

# Get episodes from a specific YouTube channel
bxc ietv channel inazumaelevenfrance1
bxc ietv channel inazumatvfr --profile fast

# Scrape official inazuma-eleven.fr streaming site (most complete)
bxc ietv official

# Aggregate all episodes from all 4 YouTube channels + official site
bxc ietv all

# Discover additional Inazuma Eleven channels via Google Search
bxc ietv discover

# Verify YouTube API credentials
bxc ietv check-auth

# List canonical YouTube channels
bxc ietv list

Channels & Sources

Official Streaming Site

  • inazuma-eleven.fr (most complete) — https://inazuma-eleven.fr/tv/watch?lang=fr
    • Official French streaming platform
    • Complete episode catalog with proper metadata

YouTube Channels

  • @inazumaelevenfrance1 — https://www.youtube.com/@inazumaelevenfrance1
  • @inazumatvfr — https://www.youtube.com/@inazumatvfr
  • @inazumaelevengofrance — https://www.youtube.com/@inazumaelevengofrance
  • @InazumaTVFR__ — https://www.youtube.com/@InazumaTVFR__

Output Format

Channel Info

{
  "channel": "inazumaelevenfrance1",
  "title": "Inazuma Eleven France officiel",
  "description": "Chaîne officielle française d'Inazuma Eleven",
  "avatar": "https://...",
  "seasons": [
    {
      "season": 1,
      "episodes": [
        {
          "title": "Inazuma Eleven - Saison 1 Episode 01",
          "videoId": "abc123def456",
          "url": "https://www.youtube.com/watch?v=abc123def456",
          "description": null,
          "thumbnail": null,
          "publishDate": null,
          "season": 1,
          "episode": 1,
          "duration": null,
          "viewCount": null
        }
        // ... more episodes
      ],
      "totalEpisodes": 51
    }
    // ... more seasons
  ],
  "totalEpisodes": 153
}

Episode & Language Parsing

Episode Numbers

Episode numbers are parsed from video titles using these patterns (in order of precedence):

  1. S##E## format: "Season 1 Episode 5" → season 1, episode 5
  2. Saison/Season X Épisode/Episode Y: "Saison 1 Épisode 5" → season 1, episode 5
  3. Ep. N format: "Inazuma Eleven Ep. 5" → season 1, episode 5 (defaults to season 1)
  4. Trailing numbers: last numeric sequence is treated as episode number

Language Versions

Videos are automatically classified as:

  • VF (Version Française) — dubbed in French

    • Detected from: "VF", "Version Française", "Doublage", "Dubbed"
    • Default when title contains "Saison"
  • VOSTFR (Version Originale Sous-Titrée Française) — original audio + French subtitles

    • Detected from: "VOSTFR", "V.O.STFR", "VO Japonaise", "Japanese Original", "JP French Subs"
    • Takes precedence over VF markers when both found
  • Unknown — unable to determine from title

This allows filtering episodes by preferred language version:

const vfOnly = episodes.filter(ep => ep.language === "vf");
const vostfrOnly = episodes.filter(ep => ep.language === "vostfr");

Options

interface IETVOptions {
  /** Transport profile: "static" (fastest, no JS), "fast", "http", "stealth", "max" */
  profile?: "static" | "http" | "fast" | "stealth" | "max";
  
  /** Per-request timeout in ms (default 30000) */
  timeoutMs?: number;
  
  /** Retries per fetch on transient failure (default 2) */
  retries?: number;
}

Note: YouTube requires JavaScript execution to load dynamic content. Default profile is "fast" which executes JavaScript. Use "static" only if you know the page has server-rendered video lists.

Exports

The module exports the following for advanced use:

// Types
export type LanguageVersion = "vf" | "vostfr" | "unknown";
export interface VideoRef { /* ... */ }
export interface SeasonInfo { /* ... */ }
export interface ChannelInfo { /* ... */ }
export interface IETVOptions { /* ... */ }

// Functions
export function parseSeasonEpisode(title: string): { season, episode }
export function detectLanguage(title: string): LanguageVersion
export class IETVScraper { /* ... */ }

Subpath exports: ./cache (SQLite cache), ./video-player, ./video-codec, ./video-search, and ./video (barrel over the three).

Video: playback & transcoding

Two optional surfaces, published under their own subpaths so the scraper stays dependency-free:

| Subpath | Purpose | Runtime deps | | --- | --- | --- | | @aphrody/ietv/video-player | HLS/MP4 playback with a skinnable UI | media-chrome, hls.js (optional peers) | | @aphrody/ietv/video-codec | Transcoding & compression profiles | mediabunny (bundled), @mediabunny/server (optional peer) | | @aphrody/ietv/video | Barrel re-exporting both plus the search layer | — |

Player (browser)

media-chrome provides the controls, hls.js the HLS protocol. Both are loaded lazily at mount time, so importing the module server-side is free.

npm install media-chrome hls.js
import { IETVPlayer } from "@aphrody/ietv/video-player";

const player = new IETVPlayer({ autoplay: false, startQuality: "auto" });
await player.mount(document.querySelector("#player")!);
await player.load("https://cdn.example/ep1.m3u8"); // format inferred from the URL

player.getQualities();     // [{ index: -1, label: "auto" }, { index: 0, label: "360p" }, …]
player.setQuality(1);      // pin a variant, or "auto" to hand back to the ABR
player.getStats();         // resolution, bitrate, measured fps, buffer health, bandwidth
player.destroy();

Native HLS (Safari) is preferred when available. Fatal hls.js errors are recovered per the upstream playbook — reload on network errors, buffer flush on media errors — and only surface to onError when unrecoverable. Without media-chrome installed the player falls back to the browser's native controls instead of failing.

Transcoding (server)

mediabunny encodes through WebCodecs. Bun and Node don't implement it, so install @mediabunny/server (FFmpeg bindings) to get encoders; ensureNativeCodecs() registers them on first use.

npm install @mediabunny/server
import {
  COMPRESSION_PROFILES,
  VideoTranscoder,
} from "@aphrody/ietv/video-codec";

const transcoder = new VideoTranscoder();

await transcoder.probe("ep1.mkv");
// { durationSeconds, video: { codec, width, height, rotation }, audio: {…} }

const result = await transcoder.transcode("ep1.mkv", "ep1.mp4", {
  profile: COMPRESSION_PROFILES.web_720,
  onProgress: (p) => process.stdout.write(`\r${(p * 100).toFixed(0)} %`),
});
// { sizeBytes, elapsedMs, videoCodec: "h265", discarded: [] }

Profiles carry both a target bitrate (bits/s) and a quantizer (FFmpeg's CRF equivalent) so the encoder can hold quality constant when it supports it:

| Profile | Codec | Resolution | Bitrate | Quantizer | | --- | --- | --- | --- | --- | | mobile_360 | H.264 / AAC | 360p | 500 kbps | 28 | | mobile_480 | H.264 / AAC | 480p | 1 Mbps | 26 | | web_720 | H.265 / AAC | 720p | 2 Mbps | 26 | | desktop_1080 | H.265 / AAC | 1080p | 4 Mbps | 24 | | av1_1080 | AV1 / Opus | 1080p | 1.5 Mbps | 32 |

VideoCodec.recommendProfile(device, bandwidthMbps) picks one, and VideoCodec.estimateFileSize(durationSeconds, profile) sizes the output before running the job. Without an available encoder, transcode() fails up front with the install hint rather than an opaque WebCodecs error.

Authentication & Credentials

The scraper automatically loads YouTube API credentials from secure sources (in order):

  1. YOUTUBE_API_KEY environment variable
  2. ~/.ietv/auth.json (JSON file with key field)
  3. ~/.aphrody/ietv-credentials.json (JSON file with youtube_api_key field)
  4. gcloud (requires gcloud auth application-default login)

Setup via Aphrody

# Create credentials directory
mkdir -p ~/.aphrody

# Store YouTube API key securely
echo '{"youtube_api_key": "YOUR_API_KEY"}' > ~/.aphrody/ietv-credentials.json
chmod 600 ~/.aphrody/ietv-credentials.json

Setup via gcloud

# Authenticate with Google Cloud
gcloud auth application-default login

# Or set service account credentials
export GOOGLE_APPLICATION_CREDENTIALS=~/.config/gcloud/service-account.json

Channel Discovery

The scraper can discover additional Inazuma Eleven channels beyond the canonical four:

const scraper = new IETVScraper();
// Credentials are auto-loaded from ~/.aphrody/ or environment
const discoveredChannels = await scraper.discoverChannels(
  "Inazuma Eleven français replay"
);

Discovery methods (in order of preference):

  1. YouTube Data API (automatic if credentials found) — most accurate, ~50 results
  2. Google Search (fallback) — finds YouTube channels via Google results, slower but free
# Discover with auto-loaded credentials
bxc ietv discover

# Or override with explicit API key
bxc ietv discover --youtube-api-key "YOUR_API_KEY"

# List credentials status
bxc ietv --check-auth

Limitations

  • YouTube's anti-bot measures may rate-limit or block requests in some profiles.
  • Episode parsing relies on title conventions; inconsistently-named videos may not parse correctly.
  • Video descriptions, durations, and view counts are extracted from HTML when available but may not be complete.
  • Discovery via Google Search is slower and may miss some channels.
  • For production use, consider using the official YouTube Data API with authentication.

License

Apache-2.0