設定難易度

世界各地の美しい風景を、iPhoneのホーム画面から眺められるライブカメラウィジェットです。

アルプスの山々や氷河、湖、街並みなど、現在の景色をライブカメラの画像から取得して表示します。

プリセットには、ツークシュピッツェ、ツェル・アム・ゼー、ガルダ湖、ヒンタートゥクス氷河などの絶景スポットを収録。「ランダム」を選べば、更新するたびに収録されているカメラの中からランダムで景色を楽しめます。

さらに、foto-webcam.euでお気に入りのライブカメラを見つけた場合は、ウィジェットパラメーターから自由に追加することもできます。

世界のどこかの「今」を、ホーム画面の小さな窓から眺めてみてください。

※このウィジェットは中サイズでの使用を推奨します。

ウィジェットの表示内容

選んだカメラの静止画像を取得します。

サイズごとの表示

  • 表示するカメラ画像は小・中・大で共通です。情報を増やすのではなく、画像の大きさや配置が変わります。
  • 景色を見やすい中サイズがおすすめです。タップするとカメラのページを開けます。

設定手順

上から順に進めてください。コードの保存と設定のコピーに必要な操作欄は、それぞれの手順の中にあります。

  1. Scriptableを用意する

    App StoreでScriptableを開き、iPhoneにインストールします。すでに入っている場合は次へ進んでください。

  2. コードをコピーして保存する

    下のコード欄の「コピー」を押します。Scriptableへ移動し、「+」から新しいスクリプトを作ってコード全文を貼り付け、世界のライブカメラなどの名前で保存してください。

    保存したらこのページに戻り、次の手順へ進みます。

    // Variables used by Scriptable.
    // These must be at the very top of the file. Do not edit.
    // icon-color: deep-blue; icon-glyph: globe;
    // Version: 1.0.0
    
    // =====================================================
    // 世界の絶景ライブカメラ
    // GEBOKU Tools
    //
    // 【中サイズ専用】
    //
    // -----------------------------------------------------
    // プリセット
    // -----------------------------------------------------
    //
    // ツークシュピッツェ
    // ツークシュピッツェ北
    // ヴァンク
    // ブルンネン
    // ツェル・アム・ゼー
    // シュレーゲイス湖
    // モーザーボーデン
    // ガルダ湖
    // マルチェージネ
    // ヒンタートゥクス氷河
    // トゥクセルタール
    // ホッホケーニッヒ
    // グラーツ
    // アンコーゲル
    // アウエルニヒ
    // トーマスエック
    // ヒンターアイス氷河
    // ペンドリング
    // グラウン
    // ランダム
    //
    // -----------------------------------------------------
    // 自由設定
    // -----------------------------------------------------
    //
    // custom:カメラID|表示名|国名
    //
    // 例:
    // custom:kalterersee|カルダーロ湖|🇮🇹 Italy
    //
    // 表示名・国名は省略可能:
    // custom:kalterersee
    //
    // カメラIDは foto-webcam.eu のURL
    //
    // https://www.foto-webcam.eu/webcam/kalterersee/
    //
    // ↑ この場合「kalterersee」
    //
    // -----------------------------------------------------
    // Source:
    // www.foto-webcam.eu
    // =====================================================
    
    
    // =====================================================
    // プリセットカメラ
    // =====================================================
    
    const CAMERAS = [
    
      {
        key: "ツークシュピッツェ",
        name: "ツークシュピッツェ",
        country: "🇩🇪 Germany",
        slug: "zugspitze"
      },
    
      {
        key: "ツークシュピッツェ北",
        name: "ツークシュピッツェ北",
        country: "🇩🇪 Germany",
        slug: "zugspitze-nord"
      },
    
      {
        key: "ヴァンク",
        name: "ヴァンク",
        country: "🇩🇪 Germany",
        slug: "wank"
      },
    
      {
        key: "ブルンネン",
        name: "ブルンネン",
        country: "🇨🇭 Switzerland",
        slug: "brunnen"
      },
    
      {
        key: "ツェル・アム・ゼー",
        name: "ツェル・アム・ゼー",
        country: "🇦🇹 Austria",
        slug: "zellamsee"
      },
    
      {
        key: "シュレーゲイス湖",
        name: "シュレーゲイス湖",
        country: "🇦🇹 Austria",
        slug: "schlegeis"
      },
    
      {
        key: "モーザーボーデン",
        name: "モーザーボーデン",
        country: "🇦🇹 Austria",
        slug: "mooserboden"
      },
    
      {
        key: "ガルダ湖",
        name: "ガルダ湖・バルドリーノ",
        country: "🇮🇹 Italy",
        slug: "bardolino"
      },
    
      {
        key: "マルチェージネ",
        name: "マルチェージネ",
        country: "🇮🇹 Italy",
        slug: "malcesine"
      },
    
      {
        key: "ヒンタートゥクス氷河",
        name: "ヒンタートゥクス氷河",
        country: "🇦🇹 Austria",
        slug: "hotel-berghaus"
      },
    
      {
        key: "トゥクセルタール",
        name: "トゥクセルタール",
        country: "🇦🇹 Austria",
        slug: "tuxertal"
      },
    
      {
        key: "ホッホケーニッヒ",
        name: "ホッホケーニッヒ",
        country: "🇦🇹 Austria",
        slug: "hochkoenig"
      },
    
      {
        key: "グラーツ",
        name: "グラーツ",
        country: "🇦🇹 Austria",
        slug: "helmut-list-halle"
      },
    
      {
        key: "アンコーゲル",
        name: "アンコーゲル",
        country: "🇦🇹 Austria",
        slug: "ankogel-nord"
      },
    
      {
        key: "アウエルニヒ",
        name: "アウエルニヒ",
        country: "🇦🇹 Austria",
        slug: "auernig"
      },
    
      {
        key: "トーマスエック",
        name: "トーマスエック",
        country: "🇦🇹 Austria",
        slug: "thomaseck"
      },
    
      {
        key: "ヒンターアイス氷河",
        name: "ヒンターアイス氷河",
        country: "🇦🇹 Austria",
        slug: "hintereisferner1"
      },
    
      {
        key: "ペンドリング",
        name: "ペンドリング",
        country: "🇦🇹 Austria",
        slug: "pendling-ost"
      },
    
      {
        key: "グラウン",
        name: "グラウン",
        country: "🇮🇹 Italy",
        slug: "graun"
      }
    
    ];
    
    
    // =====================================================
    // Parameter
    // =====================================================
    
    const PARAM =
      (args.widgetParameter || "").trim();
    
    
    // =====================================================
    // カスタム設定解析
    // =====================================================
    
    function parseCustomCamera(param) {
    
      if (
        !param.toLowerCase().startsWith("custom:")
      ) {
        return null;
      }
    
    
      // custom: を削除
      const value =
        param.substring(7).trim();
    
    
      // | で分割
      const parts =
        value.split("|");
    
    
      // ---------------------------------------------
      // カメラID
      // ---------------------------------------------
    
      let slug =
        (parts[0] || "").trim();
    
    
      // URLそのものを貼った場合にも対応
      //
      // https://www.foto-webcam.eu/webcam/kalterersee/
      // ↓
      // kalterersee
      // ---------------------------------------------
    
      const urlMatch =
        slug.match(
          /foto-webcam\.eu\/webcam\/([^\/?#]+)/i
        );
    
    
      if (urlMatch) {
    
        slug =
          urlMatch[1];
      }
    
    
      // ---------------------------------------------
      // 安全のため余計な文字を削除
      // ---------------------------------------------
    
      slug =
        slug
          .replace(/^\/+/, "")
          .replace(/\/+$/, "")
          .trim();
    
    
      if (!slug) {
        return null;
      }
    
    
      // ---------------------------------------------
      // 表示名
      // ---------------------------------------------
    
      const name =
        (parts[1] || "").trim()
        ||
        slug;
    
    
      // ---------------------------------------------
      // 国名
      // ---------------------------------------------
    
      const country =
        (parts[2] || "").trim()
        ||
        "Custom Camera";
    
    
      return {
    
        key: "custom",
    
        name: name,
    
        country: country,
    
        slug: slug,
    
        custom: true
    
      };
    }
    
    
    // =====================================================
    // カメラ選択
    // =====================================================
    
    function selectCamera() {
    
      // ===================================================
      // カスタム
      // ===================================================
    
      const custom =
        parseCustomCamera(
          PARAM
        );
    
    
      if (custom) {
    
        return custom;
      }
    
    
      // ===================================================
      // 未入力 / ランダム
      // ===================================================
    
      if (
        !PARAM ||
        PARAM === "ランダム"
      ) {
    
        const index =
          Math.floor(
            Math.random() *
            CAMERAS.length
          );
    
    
        return CAMERAS[index];
      }
    
    
      // ===================================================
      // プリセット
      // ===================================================
    
      const camera =
        CAMERAS.find(
          item =>
            item.key === PARAM
        );
    
    
      return camera || CAMERAS[0];
    }
    
    
    const CAMERA =
      selectCamera();
    
    
    // =====================================================
    // URL
    // =====================================================
    
    const CAMERA_URL =
      `https://www.foto-webcam.eu/webcam/${CAMERA.slug}/`;
    
    
    const IMAGE_URL =
      `${CAMERA_URL}current/400.jpg`;
    
    
    // =====================================================
    // キャッシュ
    // =====================================================
    
    const fm =
      FileManager.local();
    
    
    const cacheDir =
      fm.joinPath(
        fm.documentsDirectory(),
        "geboku_world_livecam"
      );
    
    
    if (
      !fm.fileExists(cacheDir)
    ) {
    
      fm.createDirectory(
        cacheDir
      );
    }
    
    
    // ---------------------------------------------
    // ファイル名として使えない文字を除去
    // ---------------------------------------------
    
    const safeSlug =
      CAMERA.slug.replace(
        /[^a-zA-Z0-9_-]/g,
        "_"
      );
    
    
    const cachePath =
      fm.joinPath(
        cacheDir,
        `${safeSlug}.jpg`
      );
    
    
    // =====================================================
    // 画像取得
    // =====================================================
    
    async function getImage() {
    
      try {
    
        // ---------------------------------------------
        // foto-webcam.eu は固定URLをそのまま取得
        // ?t=... は付けない
        // ---------------------------------------------
    
        const req =
          new Request(
            IMAGE_URL
          );
    
    
        req.timeoutInterval =
          20;
    
    
        const image =
          await req.loadImage();
    
    
        console.log(
          `取得成功:${CAMERA.name}`
        );
    
    
        console.log(
          `Camera ID:${CAMERA.slug}`
        );
    
    
        console.log(
          `${image.size.width} × ${image.size.height}`
        );
    
    
        // ---------------------------------------------
        // キャッシュ
        // ---------------------------------------------
    
        fm.writeImage(
          cachePath,
          image
        );
    
    
        return {
    
          image: image,
    
          cached: false
    
        };
    
    
      } catch (error) {
    
        console.log(
          `取得失敗:${CAMERA.name}`
        );
    
    
        console.log(
          `Camera ID:${CAMERA.slug}`
        );
    
    
        console.log(
          error
        );
    
    
        // ---------------------------------------------
        // 前回画像
        // ---------------------------------------------
    
        if (
          fm.fileExists(cachePath)
        ) {
    
          return {
    
            image:
              fm.readImage(
                cachePath
              ),
    
            cached: true
    
          };
        }
    
    
        return {
    
          image: null,
    
          cached: false
    
        };
      }
    }
    
    
    // =====================================================
    // Widget
    // =====================================================
    
    async function createWidget() {
    
      const widget =
        new ListWidget();
    
    
      widget.setPadding(
        0,
        0,
        0,
        0
      );
    
    
      widget.backgroundColor =
        Color.black();
    
    
      // ===================================================
      // タップ
      // ===================================================
    
      widget.url =
        CAMERA_URL;
    
    
      // ===================================================
      // 画像取得
      // ===================================================
    
      const result =
        await getImage();
    
    
      // ===================================================
      // 背景
      // ===================================================
    
      if (
        result.image
      ) {
    
        widget.backgroundImage =
          result.image;
    
      }
    
    
      // ===================================================
      // エラー
      // ===================================================
    
      else {
    
        const errorStack =
          widget.addStack();
    
    
        errorStack.layoutVertically();
    
    
        errorStack.addSpacer();
    
    
        const error =
          errorStack.addText(
            "画像を取得できません"
          );
    
    
        error.textColor =
          Color.white();
    
    
        error.font =
          Font.mediumSystemFont(
            12
          );
    
    
        error.centerAlignText();
    
    
        errorStack.addSpacer();
      }
    
    
      // ===================================================
      // 情報
      // ===================================================
    
      const main =
        widget.addStack();
    
    
      main.layoutVertically();
    
    
      main.setPadding(
        10,
        12,
        10,
        12
      );
    
    
      main.addSpacer();
    
    
      const info =
        main.addStack();
    
    
      info.layoutHorizontally();
    
    
      info.centerAlignContent();
    
    
      // ===================================================
      // 左:場所
      // ===================================================
    
      const location =
        info.addStack();
    
    
      location.layoutVertically();
    
    
      // ---------------------------------------------
      // 場所名
      // ---------------------------------------------
    
      const name =
        location.addText(
          CAMERA.name
        );
    
    
      name.font =
        Font.boldSystemFont(
          13
        );
    
    
      name.textColor =
        Color.white();
    
    
      name.shadowColor =
        new Color(
          "#000000",
          0.95
        );
    
    
      name.shadowRadius =
        3;
    
    
      name.lineLimit =
        1;
    
    
      // ---------------------------------------------
      // 国
      // ---------------------------------------------
    
      const country =
        location.addText(
          CAMERA.country
        );
    
    
      country.font =
        Font.mediumSystemFont(
          9
        );
    
    
      country.textColor =
        new Color(
          "#ffffff",
          0.9
        );
    
    
      country.shadowColor =
        new Color(
          "#000000",
          0.95
        );
    
    
      country.shadowRadius =
        2;
    
    
      // ===================================================
      // 右
      // ===================================================
    
      info.addSpacer();
    
    
      const source =
        info.addText(
    
          result.cached
    
            ? "LAST IMAGE\nwww.foto-webcam.eu"
    
            : "● LIVE\nwww.foto-webcam.eu"
    
        );
    
    
      source.font =
        Font.mediumSystemFont(
          8
        );
    
    
      source.textColor =
        Color.white();
    
    
      source.rightAlignText();
    
    
      source.shadowColor =
        new Color(
          "#000000",
          0.95
        );
    
    
      source.shadowRadius =
        2;
    
    
      source.url =
        CAMERA_URL;
    
    
      // ===================================================
      // 更新
      // ===================================================
    
      widget.refreshAfterDate =
        new Date(
          Date.now() +
          10 * 60 * 1000
        );
    
    
      return widget;
    }
    
    
    // =====================================================
    // 実行
    // =====================================================
    
    const widget =
      await createWidget();
    
    
    if (
      config.runsInWidget
    ) {
    
      Script.setWidget(
        widget
      );
    
    } else {
    
      await widget.presentMedium();
    
    }
    
    
    Script.complete();
  3. ウィジェットの設定を作る

    下の入力欄で自分用の設定を作り、生成されたパラメーターをコピーします。詳しい設定仕様は、設定手順の下でも確認できます。

    ライブカメラ パラメーター作成

    表示したいライブカメラを選ぶと、ウィジェット用のパラメーターを自動で作成します。

    カメラの設定

    ライブカメラを選択

    生成されたパラメーター


    コピーできたら、次の手順でホーム画面のウィジェットへ貼り付けます。

  4. ホーム画面に追加して設定する

    iPhoneのホーム画面を長押しし、ウィジェット追加画面からScriptableを選びます。この記事の対応サイズの説明に合わせてサイズを選んで追加してください。

    追加したウィジェットを長押しして「ウィジェットを編集」を開き、「Script」に保存したスクリプトを選びます。

    「Parameter」に直前の手順でコピーした値を貼り付けます。設定を変えるときも、同じ入力欄で作り直して貼り替えてください。

    編集画面を閉じて表示を確認します。ホーム画面への反映には少し時間がかかる場合があります。

パラメータ・カスタマイズ

パラメーターを追加する

3つの設定方法があります。ご希望に応じて設定してください。

  • プリセットで入っている場所の名前を入力する方法
  • foto-webcam.eu で公開されている好きなライブカメラを設定する方法
  • 何も入力しないでプリセットをランダム表示する方法

下記の「ライブカメラ パラメーター作成」を使って必要な項目を入力すると、画面下に「生成されたパラメーター」が自動で表示されます。

「パラメーターをコピー」を押してください。

そのままiPhoneのウィジェット設定の「parameter」枠に貼り付けたら設定完了です。

ライブカメラについて

ライブ画像には foto-webcam.eu が提供している現在画像を使用しています。
画像をタップすると、そのライブカメラの公式ページを開きます。
通信できなかった場合は、前回正常に取得した画像を表示します。
※ライブカメラの稼働状況や更新頻度によって、画像が更新されない場合があります。

画像提供:www.foto-webcam.eu

動作確認・使用結果

確認した端末 iPhone 17 ProMAX
iOS iOS27
Scriptable 1.7.19
確認したサイズ 小・中・大
バージョン 1.0.0
動作確認日 2026-09-26

表示・使用時のポイント

選んだカメラの静止画像を取得します。プリセット・任意の対応カメラ・ランダム選択を使え、取得できない場合は保存済み画像を使うことがあります。

更新履歴

1.0.0

  • 初回リリース

よくあるエラーと対処

確認すること
表示されない場合は、ウィジェット編集画面の「Script」に保存したスクリプトが選ばれているかも確認してください。コードを貼り直すときは全文を置き換えます。
好きなカメラが表示されない
対応するfoto-webcam.euのカメラURLや識別子を指定してください。任意の動画サイトURLには対応しません。
画像が変わらない
配信元の撮影間隔とウィジェットの更新間隔によります。保存画像を使っている場合もあります。
暗い画像になる
現地が夜間の場合があります。カメラの現地時刻も確認してください。

注意事項・制限

  • 映像を連続再生する動画プレーヤーではなく、更新された静止画像の表示です。
  • カメラの稼働状況・画像の利用条件は配信元によります。
  • ホーム画面の更新タイミングはiOSが管理するため、操作やデータの変更がすぐに反映されない場合があります。

データ提供・参考資料

foto-webcam.eu:ライブカメラ画像とカメラ情報。

SHARE

この記事をシェア