diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000000000000000000000000000000000..dbde76fe50fd14262a5e24617b83d53b677efca1 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +*.png filter=lfs diff=lfs merge=lfs -text +*.zip filter=lfs diff=lfs merge=lfs -text +*.mp3 filter=lfs diff=lfs merge=lfs -text +*.mp4 filter=lfs diff=lfs merge=lfs -text +*.jpeg filter=lfs diff=lfs merge=lfs -text +*.jpg filter=lfs diff=lfs merge=lfs -text +*.wav filter=lfs diff=lfs merge=lfs -text +*.ico filter=lfs diff=lfs merge=lfs -text +*.wasm filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..d186f21a21dc824a9c6bdfaaf3c2b99a78643ecc --- /dev/null +++ b/.gitignore @@ -0,0 +1,53 @@ +# secret codes +lib/config/secrets.dart +run_locally_with_secrets.sh + +# Python venv +.python_venv/ + +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ +!/build/web + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release + +/assets/config/ \ No newline at end of file diff --git a/.metadata b/.metadata new file mode 100644 index 0000000000000000000000000000000000000000..d044da85245f583954a96bee75a1f23e9b5ce1ae --- /dev/null +++ b/.metadata @@ -0,0 +1,45 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 + base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 + - platform: android + create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 + base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 + - platform: ios + create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 + base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 + - platform: linux + create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 + base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 + - platform: macos + create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 + base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 + - platform: web + create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 + base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 + - platform: windows + create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 + base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md new file mode 100644 index 0000000000000000000000000000000000000000..8783e0330f5b13869711684a7829f81b0a5e70bb --- /dev/null +++ b/DEPLOYMENT.md @@ -0,0 +1,73 @@ + +## Deploying aitube2 to https://aitube.at + +Note: this document is meant for aitube administrators only, not the general public. + +Aitube is not an app/tool but a website, it is not designed to be cloned (technically you can, but since this is not a goal it is not documented). + +### Setup the domain + +TODO + +### Seting up the Python virtual environment + +```bash +python3 -m venv .python_venv +source .python_venv/bin/activate +python3 -m pip install --no-cache-dir --upgrade -r requirements.txt +``` + +### Deployment to production + +To deploy the aitube2 api to production: + + $ git push space main + +To deploy the aitube2 client to production, simply run: + + $ flutter run web + +and upload the assets to: + + https://huggingface.co/spaces/jbilcke-hf/aitube2/tree/main/public + +#### Running a rendering node + +Current aitube uses `jbilcke-hf/LTX-Video-0-9-6-HFIE` as a rendering node. + +aitube uses a round-robin schedule implemented on the gateway. +This helps ensuring a smooth attribution of requests. + +What works well is to use the target number of users in parallel (eg. 3) and use 50% more capability to make sure we can handle the load, so in this case about 5 servers. + +```bash +# note: you need to replace , and + +curl https://api.endpoints.huggingface.cloud/v2/endpoint/ -X POST -d '{"cacheHttpResponses":false,"compute":{"accelerator":"gpu","instanceSize":"x1","instanceType":"nvidia-l40s","scaling":{"maxReplica":1,"measure":{"hardwareUsage":80},"minReplica":0,"scaleToZeroTimeout":120,"metric":"hardwareUsage"}},"model":{"env":{},"framework":"custom","image":{"huggingface":{}},"repository":"jbilcke-hf/LTX-Video-0-9-6-HFIE","secrets":{},"task":"custom","fromCatalog":false},"name":"ltx-video-0-9-6-round-robin-","provider":{"region":"us-east-1","vendor":"aws"},"tags":[""],"type":"protected"}' -H "Content-Type: application/json" -H "Authorization: Bearer " +``` + +#### Running the gateway scheduler + +```bash +# load the environment +# (if you haven't done it already for this shell session) +source .python_venv/bin/activate + +HF_TOKEN="" \ + SECRET_TOKEN="" \ + VIDEO_ROUND_ROBIN_SERVER_1="https:/.endpoints.huggingface.cloud" \ + VIDEO_ROUND_ROBIN_SERVER_2="https://.endpoints.huggingface.cloud" \ + VIDEO_ROUND_ROBIN_SERVER_3="https://.endpoints.huggingface.cloud" \ + VIDEO_ROUND_ROBIN_SERVER_4="https://.endpoints.huggingface.cloud" \ + HF_IMAGE_MODEL="https://.endpoints.huggingface.cloud" \ + HF_TEXT_MODEL="https://.endpoints.huggingface.cloud" \ + python3 api.py +``` + +### Run the client (web) + +```bash + +flutter run --dart-define=CONFIG_PATH=assets/config/aitube_low.yaml -d chrome +``` + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..427f95c1602875b9f737a838afd29ae250e84831 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,52 @@ +FROM nvidia/cuda:12.4.0-devel-ubuntu22.04 + +ARG DEBIAN_FRONTEND=noninteractive + +ENV PYTHONUNBUFFERED=1 + +RUN apt-get update && apt-get install --no-install-recommends -y \ + build-essential \ + python3.11 \ + python3-pip \ + python3-dev \ + git \ + curl \ + ffmpeg \ + libglib2.0-0 \ + libsm6 \ + libxrender1 \ + libxext6 \ + && apt-get clean && rm -rf /var/lib/apt/lists/* + +WORKDIR /code + +COPY ./requirements.txt /code/requirements.txt + +# Set up a new user named "user" with user ID 1000 +RUN useradd -m -u 1000 user +# Switch to the "user" user +USER user +# Set home to the user's home directory +ENV HOME=/home/user \ + PATH=/home/user/.local/bin:$PATH + +# Set home to the user's home directory +ENV PYTHONPATH=$HOME/app \ + PYTHONUNBUFFERED=1 \ + DATA_ROOT=/tmp/data + +RUN echo "Installing requirements.txt" +RUN pip3 install --no-cache-dir --upgrade -r /code/requirements.txt + +# yeah.. this is manual for now +#RUN flutter build web + +WORKDIR $HOME/app + +COPY --chown=user . $HOME/app + +EXPOSE 8080 + +ENV PORT 8080 + +CMD python3 api.py diff --git a/NOTES.md b/NOTES.md new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..a07b55da1b2058dd0fd4d5afce253616b4a2381f --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +--- +title: aitube2 +emoji: 🎥 +colorFrom: red +colorTo: yellow +sdk: docker +app_file: api.py +pinned: true +short_description: A Latent YouTube +--- + + +# AiTube2 + +## News + +aitube2 is coming sooner than expected! + +Stay hooked at @flngr on X! + + +## What is AiTube? + +AiTube 2 is a reboot of [AiTube 1](https://x.com/danielpikl/status/1737882643625078835), a project made in 2023 which generated AI videos in the background using LLM agents, to simulate an AI generated YouTube. + +In [AiTube 2](https://x.com/flngr/status/1864127796945011016), this concept is put upside down: now the content is generated on demand (when the user types something in the latent search input) and on the fly (video chunks are generated within a few seconds and streamed continuously). + +This allows for new ways of consuming AI generated content, such as collaborative and interactive prompting. + +# Where can I use it? + +AiTube 2 is not ready yet: this is an experimental side project and the [platform](https://aitube.at), code and documentation will be in development for most of 2025. + +# Why can't I use it? + +As this is a personal project I only have limited ressources to develop it on the side, but there are also technological bottlenecks. + +Right now it is not economically viable to operate a platform like AiTube, it requires hardware that is too expensive and/or not powerful enough to give an enjoyable and reactive streaming experience. + +I am evaluating various options to make it available sooner for people with the financial ressources to try it, such as creating a system to deploy render nodes to Hugging Face, GPU-on-demand blockchains.. etc. + +# When can I use it? + +I estimate it will take up to 1 to 2 years for more powerful and/or cheaper hardware to become available. + +I already have a open-source prototype of AiTube which I use for R&D, based on free (as in "can run on your own machine") AI video models that can run fast with low quality settings (such as LTX Video). + +It's not representative of the final experience, but that's a start and I use that as a basis to imagine the experiences of the future (collaborative generation, broadcasted live streams, interactive gaming, and artistic experiences that are hybrid between video and gaming). diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000000000000000000000000000000000000..0d2902135caece481a035652d88970c80e29cc7e --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..55afd919c6591c63bfcfda8a5ce8a60acc7666ca --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/android/app/build.gradle b/android/app/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..1bebf2a3b62af88c872e8beb4954569a4aa0c56d --- /dev/null +++ b/android/app/build.gradle @@ -0,0 +1,44 @@ +plugins { + id "com.android.application" + id "kotlin-android" + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id "dev.flutter.flutter-gradle-plugin" +} + +android { + namespace = "com.example.aitube2" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8 + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.aitube2" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.debug + } + } +} + +flutter { + source = "../.." +} diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..399f6981d5d35475eb18e6068ae67cdd7c731978 --- /dev/null +++ b/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..70880b8488ffb456bf77c6c1e6a171211084640e --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/kotlin/com/example/aitube2/MainActivity.kt b/android/app/src/main/kotlin/com/example/aitube2/MainActivity.kt new file mode 100644 index 0000000000000000000000000000000000000000..7b95c817322112d3d89b88cdc18d1b96b28e5fdb --- /dev/null +++ b/android/app/src/main/kotlin/com/example/aitube2/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.aitube2 + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000000000000000000000000000000000000..f74085f3f6a2b995f8ad1f9ff7b2c46dc118a9e0 --- /dev/null +++ b/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000000000000000000000000000000000000..304732f8842013497e14bd02f67a55f2614fb8f7 --- /dev/null +++ b/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..1be8cfe844b15e05c9de3599e39fc8ece41ecb5d --- /dev/null +++ b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6a7c8f0d703e3682108f9662f813302236240d3f8f638bb391e32bfb96055fef +size 544 diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..87515b2d645d212f2e45a76da6f549df4ed15f3c --- /dev/null +++ b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c7c0c0189145e4e32a401c61c9bdc615754b0264e7afae24e834bb81049eaf81 +size 442 diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..8dab3b8d08329295ce8af5207edc3e3763974909 --- /dev/null +++ b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e14aa40904929bf313fded22cf7e7ffcbf1d1aac4263b5ef1be8bfce650397aa +size 721 diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..7172d5a93bb1db937dda60ff09b182d0ca16657c --- /dev/null +++ b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4d470bf22d5c17d84edc5f82516d1ba8a1c09559cd761cefb792f86d9f52b540 +size 1031 diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4183fcce1c2d121a39442793d55184f26e14e50d --- /dev/null +++ b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3c34e1f298d0c9ea3455d46db6b7759c8211a49e9ec6e44b635fc5c87dfb4180 +size 1443 diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000000000000000000000000000000000000..06952be745f9fa6fa75196e830d9578eb2ee631d --- /dev/null +++ b/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000000000000000000000000000000000000..cb1ef88056edd1caf99a935e434e7ff6943a0ef6 --- /dev/null +++ b/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..399f6981d5d35475eb18e6068ae67cdd7c731978 --- /dev/null +++ b/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..d2ffbffa4cd251cc00b2b93a5efc2a0213460220 --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,18 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = "../build" +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000000000000000000000000000000000000..2597170821647d2bd8b150d973a35d27c99a5f44 --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000000000000000000000000000000000..7bb2df6ba6ea53ebbee820728a3eef274ddd71bd --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip diff --git a/android/settings.gradle b/android/settings.gradle new file mode 100644 index 0000000000000000000000000000000000000000..b9e43bd37614c4e28ab1d41c3d6a262832ce26e4 --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1,25 @@ +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.1.0" apply false + id "org.jetbrains.kotlin.android" version "1.8.22" apply false +} + +include ":app" diff --git a/api.py b/api.py new file mode 100644 index 0000000000000000000000000000000000000000..11c7a889bd0f961948f9a8655ed5105a25708ccd --- /dev/null +++ b/api.py @@ -0,0 +1,445 @@ +import asyncio +import json +import logging +import os +import pathlib +from aiohttp import web, WSMsgType +from typing import Dict, Any +from api_core import VideoGenerationAPI + +from api_config import * + +# Configure logging +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' +) +logger = logging.getLogger(__name__) + +async def process_generic_request(data: dict, ws: web.WebSocketResponse, api) -> None: + """Handle general requests that don't fit into specialized queues""" + try: + request_id = data.get('requestId') + action = data.get('action') + + def error_response(message: str): + return { + 'action': action, + 'requestId': request_id, + 'success': False, + 'error': message + } + + if action == 'heartbeat': + # Include user role info in heartbeat response + user_role = getattr(ws, 'user_role', 'anon') + await ws.send_json({ + 'action': 'heartbeat', + 'requestId': request_id, + 'success': True, + 'user_role': user_role + }) + + elif action == 'get_user_role': + # Return the user role information + user_role = getattr(ws, 'user_role', 'anon') + await ws.send_json({ + 'action': 'get_user_role', + 'requestId': request_id, + 'success': True, + 'user_role': user_role + }) + + elif action == 'generate_caption': + title = data.get('params', {}).get('title') + description = data.get('params', {}).get('description') + + if not title or not description: + await ws.send_json(error_response('Missing title or description')) + return + + caption = await api.generate_caption(title, description) + await ws.send_json({ + 'action': action, + 'requestId': request_id, + 'success': True, + 'caption': caption + }) + + elif action == 'generate_thumbnail': + title = data.get('params', {}).get('title') + description = data.get('params', {}).get('description') + + if not title or not description: + await ws.send_json(error_response('Missing title or description')) + return + + thumbnail = await api.generate_thumbnail(title, description) + await ws.send_json({ + 'action': action, + 'requestId': request_id, + 'success': True, + 'thumbnailUrl': thumbnail + }) + + else: + await ws.send_json(error_response(f'Unknown action: {action}')) + + except Exception as e: + logger.error(f"Error processing generic request: {str(e)}") + try: + await ws.send_json({ + 'action': data.get('action'), + 'requestId': data.get('requestId'), + 'success': False, + 'error': f'Internal server error: {str(e)}' + }) + except Exception as send_error: + logger.error(f"Error sending error response: {send_error}") + +async def process_search_queue(queue: asyncio.Queue, ws: web.WebSocketResponse, api): + """Medium priority queue for search operations""" + while True: + try: + data = await queue.get() + request_id = data.get('requestId') + query = data.get('query', '').strip() + search_count = data.get('searchCount', 0) + attempt_count = data.get('attemptCount', 0) + + logger.info(f"Processing search request: query='{query}', search_count={search_count}, attempt={attempt_count}") + + if not query: + logger.warning(f"Empty query received in request: {data}") + result = { + 'action': 'search', + 'requestId': request_id, + 'success': False, + 'error': 'No search query provided' + } + else: + try: + search_result = await api.search_video( + query, + search_count=search_count, + attempt_count=attempt_count + ) + + if search_result: + logger.info(f"Search successful for query '{query}' (#{search_count})") + result = { + 'action': 'search', + 'requestId': request_id, + 'success': True, + 'result': search_result + } + else: + logger.warning(f"No results found for query '{query}' (#{search_count})") + result = { + 'action': 'search', + 'requestId': request_id, + 'success': False, + 'error': 'No results found' + } + except Exception as e: + logger.error(f"Search error for query '{query}' (#{search_count}, attempt {attempt_count}): {str(e)}") + result = { + 'action': 'search', + 'requestId': request_id, + 'success': False, + 'error': f'Search error: {str(e)}' + } + + await ws.send_json(result) + + except Exception as e: + logger.error(f"Error in search queue processor: {str(e)}") + try: + error_response = { + 'action': 'search', + 'requestId': data.get('requestId') if 'data' in locals() else None, + 'success': False, + 'error': f'Internal server error: {str(e)}' + } + await ws.send_json(error_response) + except Exception as send_error: + logger.error(f"Error sending error response: {send_error}") + finally: + if 'queue' in locals(): + queue.task_done() + +async def process_chat_queue(queue: asyncio.Queue, ws: web.WebSocketResponse): + """High priority queue for chat operations""" + while True: + data = await queue.get() + try: + api = ws.app['api'] + if data['action'] == 'join_chat': + result = await api.handle_join_chat(data, ws) + elif data['action'] == 'chat_message': + result = await api.handle_chat_message(data, ws) + elif data['action'] == 'leave_chat': + result = await api.handle_leave_chat(data, ws) + await ws.send_json(result) + except Exception as e: + logger.error(f"Error processing chat request: {e}") + try: + await ws.send_json({ + 'action': data['action'], + 'requestId': data.get('requestId'), + 'success': False, + 'error': f'Chat error: {str(e)}' + }) + except Exception as send_error: + logger.error(f"Error sending error response: {send_error}") + finally: + queue.task_done() + +async def process_video_queue(queue: asyncio.Queue, ws: web.WebSocketResponse): + """Process multiple video generation requests in parallel""" + active_tasks = set() + MAX_CONCURRENT = len(VIDEO_ROUND_ROBIN_ENDPOINT_URLS) # Match client's max concurrent generations + + async def process_single_request(data): + try: + api = ws.app['api'] + title = data.get('title', '') + description = data.get('description', '') + video_prompt_prefix = data.get('video_prompt_prefix', '') + options = data.get('options', {}) + + # Get the user role from the websocket + user_role = getattr(ws, 'user_role', 'anon') + + # Pass the user role to generate_video + video_data = await api.generate_video(title, description, video_prompt_prefix, options, user_role) + + result = { + 'action': 'generate_video', + 'requestId': data.get('requestId'), + 'success': True, + 'video': video_data, + } + + await ws.send_json(result) + + except Exception as e: + logger.error(f"Error processing video request: {e}") + try: + await ws.send_json({ + 'action': 'generate_video', + 'requestId': data.get('requestId'), + 'success': False, + 'error': f'Video generation error: {str(e)}' + }) + except Exception as send_error: + logger.error(f"Error sending error response: {send_error}") + finally: + active_tasks.discard(asyncio.current_task()) + + while True: + # Clean up completed tasks + active_tasks = {task for task in active_tasks if not task.done()} + + # Start new tasks if we have capacity + while len(active_tasks) < MAX_CONCURRENT: + try: + # Use try_get to avoid blocking if queue is empty + data = await asyncio.wait_for(queue.get(), timeout=0.1) + + # Create and start new task + task = asyncio.create_task(process_single_request(data)) + active_tasks.add(task) + + except asyncio.TimeoutError: + # No items in queue, break inner loop + break + except Exception as e: + logger.error(f"Error creating video generation task: {e}") + break + + # Wait a short time before checking queue again + await asyncio.sleep(0.1) + + # Handle any completed tasks' errors + for task in list(active_tasks): + if task.done(): + try: + await task + except Exception as e: + logger.error(f"Task failed with error: {e}") + active_tasks.discard(task) + +async def status_handler(request: web.Request) -> web.Response: + """Handler for API status endpoint""" + api = request.app['api'] + return web.json_response({ + 'product': PRODUCT_NAME, + 'version': '0.1.0', + 'maintenance_mode': MAINTENANCE_MODE, + 'available_endpoints': len(VIDEO_ROUND_ROBIN_ENDPOINT_URLS) + }) + +async def websocket_handler(request: web.Request) -> web.WebSocketResponse: + # Check if maintenance mode is enabled + if MAINTENANCE_MODE: + # Return an error response indicating maintenance mode + return web.json_response({ + 'error': 'Server is in maintenance mode', + 'maintenance': True + }, status=503) # 503 Service Unavailable + + ws = web.WebSocketResponse( + max_msg_size=1024*1024*10, # 10MB max message size + timeout=30.0 # we want to keep things tight and short + ) + + ws.app = request.app + await ws.prepare(request) + api = request.app['api'] + + # Get the Hugging Face token from query parameters + hf_token = request.query.get('hf_token', '') + + # Validate the token and determine the user role + user_role = await api.validate_user_token(hf_token) + logger.info(f"User connected with role: {user_role}") + + # Store the user role in the websocket + ws.user_role = user_role + + # Create separate queues for different request types + chat_queue = asyncio.Queue() + video_queue = asyncio.Queue() + search_queue = asyncio.Queue() + + # Start background tasks for handling different request types + background_tasks = [ + asyncio.create_task(process_chat_queue(chat_queue, ws)), + asyncio.create_task(process_video_queue(video_queue, ws)), + asyncio.create_task(process_search_queue(search_queue, ws, api)) + ] + + try: + async for msg in ws: + if msg.type == WSMsgType.TEXT: + try: + data = json.loads(msg.data) + action = data.get('action') + + # Route requests to appropriate queues + if action in ['join_chat', 'leave_chat', 'chat_message']: + await chat_queue.put(data) + elif action in ['generate_video']: + await video_queue.put(data) + elif action == 'search': + await search_queue.put(data) + else: + await process_generic_request(data, ws, api) + + except Exception as e: + logger.error(f"Error processing WebSocket message: {str(e)}") + await ws.send_json({ + 'action': data.get('action') if 'data' in locals() else 'unknown', + 'success': False, + 'error': f'Error processing message: {str(e)}' + }) + + elif msg.type in (WSMsgType.ERROR, WSMsgType.CLOSE): + break + + finally: + # Cleanup + for task in background_tasks: + task.cancel() + try: + await asyncio.gather(*background_tasks, return_exceptions=True) + except asyncio.CancelledError: + pass + + return ws + +async def init_app() -> web.Application: + app = web.Application( + client_max_size=1024**2*10 # 10MB max size + ) + + # Create API instance + api = VideoGenerationAPI() + app['api'] = api + + # Add cleanup logic + async def cleanup_api(app): + # Add any necessary cleanup for the API + pass + + app.on_shutdown.append(cleanup_api) + + # Add routes + app.router.add_get('/ws', websocket_handler) + app.router.add_get('/api/status', status_handler) + + # Set up static file serving + # Define the path to the public directory + public_path = pathlib.Path(__file__).parent / 'build' / 'web' + if not public_path.exists(): + public_path.mkdir(parents=True, exist_ok=True) + + # Set up static file serving with proper security considerations + async def static_file_handler(request): + # Get the path from the request (removing leading /) + path_parts = request.path.lstrip('/').split('/') + + # Convert to safe path to prevent path traversal attacks + safe_path = public_path.joinpath(*path_parts) + + # Make sure the path is within the public directory (prevent directory traversal) + try: + safe_path = safe_path.resolve() + if not str(safe_path).startswith(str(public_path.resolve())): + return web.HTTPForbidden(text="Access denied") + except (ValueError, FileNotFoundError): + return web.HTTPNotFound() + + # If path is a directory, look for index.html + if safe_path.is_dir(): + safe_path = safe_path / 'index.html' + + # Check if the file exists + if not safe_path.exists() or not safe_path.is_file(): + # If not found, serve index.html (for SPA routing) + safe_path = public_path / 'index.html' + if not safe_path.exists(): + return web.HTTPNotFound() + + # Determine content type based on file extension + content_type = 'text/plain' + ext = safe_path.suffix.lower() + if ext == '.html': + content_type = 'text/html' + elif ext == '.js': + content_type = 'application/javascript' + elif ext == '.css': + content_type = 'text/css' + elif ext in ('.jpg', '.jpeg'): + content_type = 'image/jpeg' + elif ext == '.png': + content_type = 'image/png' + elif ext == '.gif': + content_type = 'image/gif' + elif ext == '.svg': + content_type = 'image/svg+xml' + elif ext == '.json': + content_type = 'application/json' + + # Return the file with appropriate headers + return web.FileResponse(safe_path, headers={'Content-Type': content_type}) + + # Add catch-all route for static files (lower priority than API routes) + app.router.add_get('/{path:.*}', static_file_handler) + + return app + +if __name__ == '__main__': + app = asyncio.run(init_app()) + web.run_app(app, host='0.0.0.0', port=8080) \ No newline at end of file diff --git a/api_config.py b/api_config.py new file mode 100644 index 0000000000000000000000000000000000000000..45625f7cf3e590cf263ed433c35fad8ae0f90b32 --- /dev/null +++ b/api_config.py @@ -0,0 +1,184 @@ +import os + +PRODUCT_NAME = os.environ.get('PRODUCT_NAME', 'AiTube') + +TEXT_MODEL = os.environ.get('HF_TEXT_MODEL', + #'HuggingFaceH4/zephyr-7b-beta' + 'HuggingFaceTB/SmolLM2-1.7B-Instruct' +) + +IMAGE_MODEL = os.environ.get('HF_IMAGE_MODEL', '') + +# Environment variable to control maintenance mode +MAINTENANCE_MODE = os.environ.get('MAINTENANCE_MODE', 'false').lower() in ('true', 'yes', '1', 't') + +ADMIN_ACCOUNTS = [ + "jbilcke-hf" +] + +RAW_VIDEO_ROUND_ROBIN_ENDPOINT_URLS = [ + os.environ.get('VIDEO_ROUND_ROBIN_SERVER_1', ''), + os.environ.get('VIDEO_ROUND_ROBIN_SERVER_2', ''), + os.environ.get('VIDEO_ROUND_ROBIN_SERVER_3', ''), + os.environ.get('VIDEO_ROUND_ROBIN_SERVER_4', ''), +] + +# Filter out empty strings from the endpoint list +VIDEO_ROUND_ROBIN_ENDPOINT_URLS = [url for url in RAW_VIDEO_ROUND_ROBIN_ENDPOINT_URLS if url] + +HF_TOKEN = os.environ.get('HF_TOKEN') + +# use the same secret token as you used to secure your BASE_SPACE_NAME spaces +SECRET_TOKEN = os.environ.get('SECRET_TOKEN') + +# altenative words we could use: "saturated, highlight, overexposed, highlighted, overlit, shaking, too bright, worst quality, inconsistent motion, blurry, jittery, distorted, cropped, watermarked, watermark, logo, subtitle, subtitles, lowres" +NEGATIVE_PROMPT = "low quality, worst quality, deformed, distorted, disfigured, blurry, text, watermark" + +POSITIVE_PROMPT_SUFFIX = "high quality, cinematic, 4K, intricate details" + +GUIDANCE_SCALE = 1.0 + +# anonymous users are people browing AiTube2 without being connected +# this category suffers from regular abuse so we need to enforce strict limitations +CONFIG_FOR_ANONYMOUS_USERS = { + + # anons can only watch 2 minutes per video + "max_rendering_time_per_client_per_video_in_sec": 2 * 60, + + "max_buffer_size": 2, + "max_concurrent_generations": 2, + + "min_num_inference_steps": 2, + "default_num_inference_steps": 2, + "max_num_inference_steps": 4, + + "min_num_frames": 9, # 8 + 1 + "default_max_num_frames": 65, # 8*8 + 1 + "max_num_frames": 65, # 8*8 + 1 + + "min_clip_duration_seconds": 1, + "default_clip_duration_seconds": 2, + "max_clip_duration_seconds": 2, + + "min_clip_playback_speed": 0.5, + "default_clip_playback_speed": 0.5, + "max_clip_playback_speed": 0.5, + + "min_clip_framerate": 8, + "default_clip_framerate": 16, + "max_clip_framerate": 16, + + "min_clip_width": 544, + "default_clip_width": 544, + "max_clip_width": 544, + + "min_clip_height": 320, + "default_clip_height": 320, + "max_clip_height": 320, +} + +# Hugging Face users enjoy a more normal and calibrated experience +CONFIG_FOR_STANDARD_HF_USERS = { + "max_rendering_time_per_client_per_video_in_sec": 15 * 60, + + "max_buffer_size": 2, + "max_concurrent_generations": 2, + + "min_num_inference_steps": 2, + "default_num_inference_steps": 4, + "max_num_inference_steps": 6, + + "min_num_frames": 9, # 8 + 1 + "default_num_frames": 65, # 8*8 + 1 + "max_num_frames": 65, # 8*8 + 1 + + "min_clip_duration_seconds": 1, + "default_clip_duration_seconds": 2, + "max_clip_duration_seconds": 2, + + "min_clip_playback_speed": 0.5, + "default_clip_playback_speed": 0.7, + "max_clip_playback_speed": 0.7, + + "min_clip_framerate": 8, + "default_clip_framerate": 25, + "max_clip_framerate": 25, + + "min_clip_width": 544, + "default_clip_width": 640, + "max_clip_width": 640, + + "min_clip_height": 320, + "default_clip_height": 416, + "max_clip_height": 416, +} + +# Hugging Face users with a Pro may enjoy an improved experience +CONFIG_FOR_PRO_HF_USERS = { + "max_rendering_time_per_client_per_video_in_sec": 20 * 60, + + "max_buffer_size": 2, + "max_concurrent_generations": 2, + + "min_num_inference_steps": 2, + "max_num_inference_steps": 8, + + "min_num_frames": 9, # 8 + 1 + "default_num_frames": 97, # (8*12) + 1 + "max_num_frames": 97, # (8*12) + 1 + + "min_clip_duration_seconds": 1, + "default_clip_duration_seconds": 2, + "max_clip_duration_seconds": 3, + + "min_clip_playback_speed": 0.5, + "default_clip_playback_speed": 0.8, + "max_clip_playback_speed": 0.8, + + "min_clip_framerate": 8, + "default_clip_framerate": 25, + "max_clip_framerate": 30, + + "min_clip_width": 544, + "default_clip_width": 768, + "max_clip_width": 768, + + "min_clip_height": 320, + "default_clip_height": 480, + "max_clip_height": 480, +} + +CONFIG_FOR_ADMIN_HF_USERS = { + "max_rendering_time_per_client_per_video_in_sec": 60 * 60, + + "max_buffer_size": 2, + "max_concurrent_generations": 2, + + "min_num_inference_steps": 2, + "default_num_inference_steps": 4, + "max_num_inference_steps": 8, + + "min_num_frames": 9, # 8 + 1 + "default_num_frames": 65, # (8 * 8) + 1 + "max_num_frames": 129, # (8 * 16) + 1 + + "min_clip_duration_seconds": 1, + "default_clip_duration_seconds": 2, + "max_clip_duration_seconds": 4, + + "min_clip_playback_speed": 0.5, + "default_clip_playback_speed": 0.8, + "max_clip_playback_speed": 0.9, + + "min_clip_framerate": 8, + "default_clip_framerate": 30, + "max_clip_framerate": 60, + + "min_clip_width": 544, + "default_clip_width": 768, + "max_clip_width": 1216, + + "min_clip_height": 320, + "default_clip_height": 480, + "max_clip_height": 704, +} diff --git a/api_core.py b/api_core.py new file mode 100644 index 0000000000000000000000000000000000000000..ab7ffd079c4e01327f257a886d1598ac007f50d9 --- /dev/null +++ b/api_core.py @@ -0,0 +1,746 @@ +import logging +import os +import io +import re +import base64 +import uuid +from typing import Dict, Any, Optional, List, Literal +from dataclasses import dataclass +from asyncio import Lock, Queue +import asyncio +import time +import datetime +from contextlib import asynccontextmanager +from collections import defaultdict +from aiohttp import web, ClientSession +from huggingface_hub import InferenceClient, HfApi +from gradio_client import Client +import random +import yaml +import json + +from api_config import * + +# User role type +UserRole = Literal['anon', 'normal', 'pro', 'admin'] + +# Configure logging +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' +) +logger = logging.getLogger(__name__) + + +def generate_seed(): + """Generate a random positive 32-bit integer seed.""" + return random.randint(0, 2**32 - 1) + +def sanitize_yaml_response(response_text: str) -> str: + """ + Sanitize and format AI response into valid YAML. + Returns properly formatted YAML string. + """ + + response_text = response_text.split("```")[0] + + # Remove any markdown code block indicators and YAML document markers + clean_text = re.sub(r'```yaml|```|---|\.\.\.$', '', response_text.strip()) + + # Split into lines and process each line + lines = clean_text.split('\n') + sanitized_lines = [] + current_field = None + + for line in lines: + stripped = line.strip() + if not stripped: + continue + + # Handle field starts + if stripped.startswith('title:') or stripped.startswith('description:'): + # Ensure proper YAML format with space after colon and proper quoting + field_name = stripped.split(':', 1)[0] + field_value = stripped.split(':', 1)[1].strip().strip('"\'') + + # Quote the value if it contains special characters + if any(c in field_value for c in ':[]{},&*#?|-<>=!%@`'): + field_value = f'"{field_value}"' + + sanitized_lines.append(f"{field_name}: {field_value}") + current_field = field_name + + elif stripped.startswith('tags:'): + sanitized_lines.append('tags:') + current_field = 'tags' + + elif stripped.startswith('-') and current_field == 'tags': + # Process tag values + tag = stripped[1:].strip().strip('"\'') + if tag: + # Clean and format tag + tag = re.sub(r'[^\x00-\x7F]+', '', tag) # Remove non-ASCII + tag = re.sub(r'[^a-zA-Z0-9\s-]', '', tag) # Keep only alphanumeric and hyphen + tag = tag.strip().lower().replace(' ', '-') + if tag: + sanitized_lines.append(f" - {tag}") + + elif current_field in ['title', 'description']: + # Handle multi-line title/description continuation + value = stripped.strip('"\'') + if value: + # Append to previous line + prev = sanitized_lines[-1] + sanitized_lines[-1] = f"{prev} {value}" + + # Ensure the YAML has all required fields + required_fields = {'title', 'description', 'tags'} + found_fields = {line.split(':')[0].strip() for line in sanitized_lines if ':' in line} + + for field in required_fields - found_fields: + if field == 'tags': + sanitized_lines.extend(['tags:', ' - default']) + else: + sanitized_lines.append(f'{field}: "No {field} provided"') + + return '\n'.join(sanitized_lines) + +@dataclass +class Endpoint: + id: int + url: str + busy: bool = False + last_used: float = 0 + +class EndpointManager: + def __init__(self): + self.endpoints: List[Endpoint] = [] + self.lock = Lock() + self.endpoint_queue: Queue[Endpoint] = Queue() + self.initialize_endpoints() + + def initialize_endpoints(self): + """Initialize the list of endpoints""" + for i, url in enumerate(VIDEO_ROUND_ROBIN_ENDPOINT_URLS): + endpoint = Endpoint(id=i + 1, url=url) + self.endpoints.append(endpoint) + self.endpoint_queue.put_nowait(endpoint) + + @asynccontextmanager + async def get_endpoint(self, max_wait_time: int = 10): + """Get the next available endpoint using a context manager""" + start_time = time.time() + endpoint = None + + try: + while True: + if time.time() - start_time > max_wait_time: + raise TimeoutError(f"Could not acquire an endpoint within {max_wait_time} seconds") + + try: + endpoint = self.endpoint_queue.get_nowait() + async with self.lock: + if not endpoint.busy: + endpoint.busy = True + endpoint.last_used = time.time() + break + else: + await self.endpoint_queue.put(endpoint) + except asyncio.QueueEmpty: + await asyncio.sleep(0.5) + continue + + yield endpoint + + finally: + if endpoint: + async with self.lock: + endpoint.busy = False + endpoint.last_used = time.time() + await self.endpoint_queue.put(endpoint) + +class ChatRoom: + def __init__(self): + self.messages = [] + self.connected_clients = set() + self.max_history = 100 + + def add_message(self, message): + self.messages.append(message) + if len(self.messages) > self.max_history: + self.messages.pop(0) + + def get_recent_messages(self, limit=50): + return self.messages[-limit:] + +class VideoGenerationAPI: + def __init__(self): + self.inference_client = InferenceClient(token=HF_TOKEN) + self.hf_api = HfApi(token=HF_TOKEN) + self.endpoint_manager = EndpointManager() + self.active_requests: Dict[str, asyncio.Future] = {} + self.chat_rooms = defaultdict(ChatRoom) + self.video_events: Dict[str, List[Dict[str, Any]]] = defaultdict(list) + self.event_history_limit = 50 + # Cache for user roles to avoid repeated API calls + self.user_role_cache: Dict[str, Dict[str, Any]] = {} + # Cache expiration time (10 minutes) + self.cache_expiration = 600 + + + def _add_event(self, video_id: str, event: Dict[str, Any]): + """Add an event to the video's history and maintain the size limit""" + events = self.video_events[video_id] + events.append(event) + if len(events) > self.event_history_limit: + events.pop(0) + + async def validate_user_token(self, token: str) -> UserRole: + """ + Validates a Hugging Face token and determines the user's role. + + Returns one of: + - 'anon': Anonymous user (no token or invalid token) + - 'normal': Standard Hugging Face user + - 'pro': Hugging Face Pro user + - 'admin': Admin user (username in ADMIN_ACCOUNTS) + """ + # If no token is provided, the user is anonymous + if not token: + return 'anon' + + # Check if we have a cached result for this token + current_time = time.time() + if token in self.user_role_cache: + cached_data = self.user_role_cache[token] + # If the cache is still valid + if current_time - cached_data['timestamp'] < self.cache_expiration: + logger.info(f"Using cached user role: {cached_data['role']}") + return cached_data['role'] + + # No valid cache, need to check the token with the HF API + try: + # Use HF API to validate the token and get user info + logger.info("Validating Hugging Face token...") + + # Run in executor to avoid blocking the event loop + user_info = await asyncio.get_event_loop().run_in_executor( + None, + lambda: self.hf_api.whoami(token=token) + ) + + logger.info(f"Token valid for user: {user_info.name}") + + # Determine the user role based on the information + user_role: UserRole + + # Check if the user is an admin + if user_info.name in ADMIN_ACCOUNTS: + user_role = 'admin' + # Check if the user has a pro account + elif hasattr(user_info, 'is_pro') and user_info.is_pro: + user_role = 'pro' + else: + user_role = 'normal' + + # Cache the result + self.user_role_cache[token] = { + 'role': user_role, + 'timestamp': current_time, + 'username': user_info.name + } + + return user_role + + except Exception as e: + logger.error(f"Failed to validate Hugging Face token: {str(e)}") + # If validation fails, the user is treated as anonymous + return 'anon' + + async def download_video(self, url: str) -> bytes: + """Download video file from URL and return bytes""" + async with ClientSession() as session: + async with session.get(url) as response: + if response.status != 200: + raise Exception(f"Failed to download video: HTTP {response.status}") + return await response.read() + + async def search_video(self, query: str, search_count: int = 0, attempt_count: int = 0) -> Optional[dict]: + """Generate a single search result using HF text generation""" + prompt = f"""# Instruction +Your response MUST be a YAML object containing a title, description, and tags, consistent with what we can find on a video sharing platform. +Format your YAML response with only those fields: "title" (single string of a short sentence), "description" (single string of a few sentences to describe the visuals), and "tags" (array of strings). Do not add any other field. +The description is a prompt for a generative AI, so please describe the visual elements of the scene in details, including: camera angle and focus, people's appearance, their age, actions, precise look, clothing, the location characteristics, lighting, action, objects, weather. +Make the result unique and different from previous search results. ONLY RETURN YAML AND WITH ENGLISH CONTENT, NOT CHINESE - DO NOT ADD ANY OTHER COMMENT! + +# Context +This is attempt {attempt_count} at generating search result number {search_count}. + +# Input +Describe the video for this theme: "{query}". +Don't forget to repeat singular elements about the characters, location.. in your description. + +# Output + +```yaml +title: \"""" + + try: + print(f"search_video(): calling self.inference_client.text_generation({prompt}, model={TEXT_MODEL}, max_new_tokens=300, temperature=0.65)") + response = await asyncio.get_event_loop().run_in_executor( + None, + lambda: self.inference_client.text_generation( + prompt, + model=TEXT_MODEL, + max_new_tokens=300, + temperature=0.6 + ) + ) + + #print("response: ", response) + + response_text = re.sub(r'^\s*\.\s*\n', '', f"title: \"{response.strip()}") + sanitized_yaml = sanitize_yaml_response(response_text) + + try: + result = yaml.safe_load(sanitized_yaml) + except yaml.YAMLError as e: + logger.error(f"YAML parsing failed: {str(e)}") + result = None + + if not result or not isinstance(result, dict): + logger.error(f"Invalid result format: {result}") + return None + + # Extract fields with defaults + title = str(result.get('title', '')).strip() or 'Untitled Video' + description = str(result.get('description', '')).strip() or 'No description available' + tags = result.get('tags', []) + + # Ensure tags is a list of strings + if not isinstance(tags, list): + tags = [] + tags = [str(t).strip() for t in tags if t and isinstance(t, (str, int, float))] + + # Generate thumbnail + #print(f"calling self.generate_thumbnail({title}, {description})") + try: + #thumbnail = await self.generate_thumbnail(title, description) + raise ValueError("thumbnail generation is too buggy and slow right now") + except Exception as e: + logger.error(f"Thumbnail generation failed: {str(e)}") + thumbnail = "" + + print("got response thumbnail") + # Return valid result with all required fields + return { + 'id': str(uuid.uuid4()), + 'title': title, + 'description': description, + 'thumbnailUrl': thumbnail, + 'videoUrl': '', + 'isLatent': True, + 'useFixedSeed': "webcam" in description.lower(), + 'seed': generate_seed(), + 'views': 0, + 'tags': tags + } + + except Exception as e: + logger.error(f"Search video generation failed: {str(e)}") + return None + + async def generate_thumbnail(self, title: str, description: str) -> str: + """Generate thumbnail using HF image generation""" + try: + image_prompt = f"Thumbnail for video titled '{title}': {description}" + + image = await asyncio.get_event_loop().run_in_executor( + None, + lambda: self.inference_client.text_to_image( + prompt=image_prompt, + model=IMAGE_MODEL, + width=1024, + height=512 + ) + ) + + buffered = io.BytesIO() + image.save(buffered, format="JPEG") + img_str = base64.b64encode(buffered.getvalue()).decode() + return f"data:image/jpeg;base64,{img_str}" + except Exception as e: + logger.error(f"Error generating thumbnail: {str(e)}") + return "" + + async def generate_caption(self, title: str, description: str) -> str: + """Generate detailed caption using HF text generation""" + try: + prompt = f"""Generate a detailed story for a video named: "{title}" +Visual description of the video: {description}. +Instructions: Write the story summary, including the plot, action, what should happen. +Make it around 200-300 words long. +A video can be anything from a tutorial, webcam, trailer, movie, live stream etc.""" + + response = await asyncio.get_event_loop().run_in_executor( + None, + lambda: self.inference_client.text_generation( + prompt, + model=TEXT_MODEL, + max_new_tokens=180, + temperature=0.7 + ) + ) + + if "Caption: " in response: + response = response.replace("Caption: ", "") + + chunks = f" {response} ".split(". ") + if len(chunks) > 1: + text = ". ".join(chunks[:-1]) + else: + text = response + + return text.strip() + except Exception as e: + logger.error(f"Error generating caption: {str(e)}") + return "" + + + def get_config_value(self, role: UserRole, field: str, options: dict = None) -> Any: + """ + Get the appropriate config value for a user role. + + Args: + role: The user role ('anon', 'normal', 'pro', 'admin') + field: The config field name to retrieve + options: Optional user-provided options that may override defaults + + Returns: + The config value appropriate for the user's role with respect to + min/max boundaries and user overrides. + """ + # Select the appropriate config based on user role + if role == 'admin': + config = CONFIG_FOR_ADMIN_HF_USERS + elif role == 'pro': + config = CONFIG_FOR_PRO_HF_USERS + elif role == 'normal': + config = CONFIG_FOR_STANDARD_HF_USERS + else: # Anonymous users + config = CONFIG_FOR_ANONYMOUS_USERS + + # Get the default value for this field from the config + default_value = config.get(f"default_{field}", None) + + # For fields that have min/max bounds + min_field = f"min_{field}" + max_field = f"max_{field}" + + # Check if min/max constraints exist for this field + has_constraints = min_field in config or max_field in config + + if not has_constraints: + # For fields without constraints, just return the value from config + return default_value + + # Get min and max values from config (if they exist) + min_value = config.get(min_field, None) + max_value = config.get(max_field, None) + + # If user provided options with this field + if options and field in options: + user_value = options[field] + + # Apply constraints if they exist + if min_value is not None and user_value < min_value: + return min_value + if max_value is not None and user_value > max_value: + return max_value + + # If within bounds, use the user's value + return user_value + + # If no user value, return the default + return default_value + + async def _generate_clip_prompt(self, video_id: str, title: str, description: str) -> str: + """Generate a new prompt for the next clip based on event history""" + events = self.video_events.get(video_id, []) + events_json = "\n".join(json.dumps(event) for event in events) + + prompt = f"""# Context and task +Please write the caption for a new clip. + +# Instructions +1. Consider the video context and recent events +2. Create a natural progression from previous clips +3. Take into account user suggestions (chat messages) into the scene +4. Don't generate hateful, political, violent or sexual content +5. Keep visual consistency with previous clips (in most cases you should repeat the same exact description of the location, characters etc but only change a few elements. If this is a webcam scenario, don't touch the camera orientation or focus) +6. Return ONLY the caption text, no additional formatting or explanation +7. Write in English, about 200 words. +8. Your caption must describe visual elements of the scene in details, including: camera angle and focus, people's appearance, age, look, costumes, clothes, the location visual characteristics and geometry, lighting, action, objects, weather, textures, lighting. + +# Examples +Here is a demo scenario, with fake data: +{{"time": "2024-11-29T13:36:15Z", "event": "new_stream_clip", "caption": "webcam view of a beautiful park, squirrels are playing in the lush grass, blablabla etc... (rest omitted for brevity)"}} +{{"time": "2024-11-29T13:36:20Z", "event": "new_chat_message", "username": "MonkeyLover89", "data": "hi"}} +{{"time": "2024-11-29T13:36:25Z", "event": "new_chat_message", "username": "MonkeyLover89", "data": "more squirrels plz"}} +{{"time": "2024-11-29T13:36:26Z", "event": "new_stream_clip", "caption": "webcam view of a beautiful park, a lot of squirrels are playing in the lush grass, blablabla etc... (rest omitted for brevity)"}} + +# Real scenario and data + +We are inside a video titled "{title}" +The video is described by: "{description}". +Here is a summary of the {len(events)} most recent events: +{events_json} + +# Your response +Your caption:""" + + try: + response = await asyncio.get_event_loop().run_in_executor( + None, + lambda: self.inference_client.text_generation( + prompt, + model=TEXT_MODEL, + max_new_tokens=200, + temperature=0.7 + ) + ) + + # Clean up the response + caption = response.strip() + if caption.lower().startswith("caption:"): + caption = caption[8:].strip() + + return caption + + except Exception as e: + logger.error(f"Error generating clip prompt: {str(e)}") + # Fallback to original description if prompt generation fails + return description + + async def generate_video(self, title: str, description: str, video_prompt_prefix: str, options: dict, user_role: UserRole = 'anon') -> str: + """Generate video using available space from pool""" + video_id = options.get('video_id', str(uuid.uuid4())) + + # Generate a new prompt based on event history + #clip_caption = await self._generate_clip_prompt(video_id, title, description) + clip_caption = f"{video_prompt_prefix} - {title.strip()} - {description.strip()}" + + # Add the new clip to event history + self._add_event(video_id, { + "time": datetime.datetime.utcnow().isoformat() + "Z", + "event": "new_stream_clip", + "caption": clip_caption + }) + + # Use the generated caption as the prompt + prompt = f"{clip_caption}, {POSITIVE_PROMPT_SUFFIX}" + + # Get the config values based on user role + width = self.get_config_value(user_role, 'clip_width', options) + height = self.get_config_value(user_role, 'clip_height', options) + num_frames = self.get_config_value(user_role, 'num_frames', options) + num_inference_steps = self.get_config_value(user_role, 'num_inference_steps', options) + frame_rate = self.get_config_value(user_role, 'clip_framerate', options) + + # Log the user role and config values being used + logger.info(f"Generating video for user with role: {user_role}") + logger.info(f"Using config values: width={width}, height={height}, num_frames={num_frames}, steps={num_inference_steps}, fps={frame_rate}") + + json_payload = { + "inputs": { + "prompt": prompt, + }, + "parameters": { + + # ------------------- settings for LTX-Video ----------------------- + + # this param doesn't exist + #"enhance_prompt_toggle": options.get('enhance_prompt', False), + + "negative_prompt": options.get('negative_prompt', NEGATIVE_PROMPT), + + # note about resolution: + # we cannot use 720 since it cannot be divided by 32 + "width": width, + "height": height, + + # this is a hack to fool LTX-Video into believing our input image is an actual video frame with poor encoding quality + #"input_image_quality": 70, + + # LTX-Video requires a frame number divisible by 8, plus one frame + # note: glitches might appear if you use more than 168 frames + "num_frames": num_frames, + + # using 30 steps seems to be enough for most cases, otherwise use 50 for best quality + # I think using a large number of steps (> 30) might create some overexposure and saturation + "num_inference_steps": num_inference_steps, + + # values between 3.0 and 4.0 are nice + "guidance_scale": options.get('guidance_scale', GUIDANCE_SCALE), + + "seed": options.get('seed', 42), + + # ---------------------------------------------------------------- + + # ------------------- settings for Varnish ----------------------- + # This will double the number of frames. + # You can activate this if you want: + # - a slow motion effect (in that case use double_num_frames=True and fps=24, 25 or 30) + # - a HD soap / video game effect (in that case use double_num_frames=True and fps=60) + "double_num_frames": False, # <- False as we want real-time generation + + # controls the number of frames per second + # use this in combination with the num_frames and double_num_frames settings to control the duration and "feel" of your video + # typical values are: 24, 25, 30, 60 + "fps": frame_rate, + + # upscale the video using Real-ESRGAN. + # This upscaling algorithm is relatively fast, + # but might create an uncanny "3D render" or "drawing" effect. + "super_resolution": False, # <- False as we want real-time generation + + # for cosmetic purposes and get a "cinematic" feel, you can optionally add some film grain. + # it is not recommended to add film grain if your theme doesn't match (film grain is great for black & white, retro looks) + # and if you do, adding more than 12% will start to negatively impact file size (video codecs aren't great are compressing film grain) + # 0% = no grain + # 10% = a bit of grain + "grain_amount": 0, # value between 0-100 + + + # The range of the CRF scale is 0–51, where: + # 0 is lossless (for 8 bit only, for 10 bit use -qp 0) + # 23 is the default + # 51 is worst quality possible + # A lower value generally leads to higher quality, and a subjectively sane range is 17–28. + # Consider 17 or 18 to be visually lossless or nearly so; + # it should look the same or nearly the same as the input but it isn't technically lossless. + # The range is exponential, so increasing the CRF value +6 results in roughly half the bitrate / file size, while -6 leads to roughly twice the bitrate. + #"quality": 18, + + } + } + + async with self.endpoint_manager.get_endpoint() as endpoint: + #logger.info(f"Using endpoint {endpoint.id} for video generation with prompt: {prompt}") + + async with ClientSession() as session: + async with session.post( + endpoint.url, + headers={ + "Accept": "application/json", + "Authorization": f"Bearer {HF_TOKEN}", + "Content-Type": "application/json" + }, + json=json_payload + ) as response: + if response.status != 200: + error_text = await response.text() + raise Exception(f"Video generation failed: HTTP {response.status} - {error_text}") + + result = await response.json() + + if "error" in result: + raise Exception(f"Video generation failed: {result['error']}") + + video_data_uri = result.get("video") + if not video_data_uri: + raise Exception("No video data in response") + + return video_data_uri + + + async def handle_chat_message(self, data: dict, ws: web.WebSocketResponse) -> dict: + """Process and broadcast a chat message""" + video_id = data.get('videoId') + request_id = data.get('requestId') + + if not video_id: + return { + 'action': 'chat_message', + 'requestId': request_id, + 'success': False, + 'error': 'No video ID provided' + } + + # Add chat message to event history + self._add_event(video_id, { + "time": datetime.datetime.utcnow().isoformat() + "Z", + "event": "new_chat_message", + "username": data.get('username', 'Anonymous'), + "data": data.get('content', '') + }) + + room = self.chat_rooms[video_id] + message_data = {k: v for k, v in data.items() if k != '_ws'} + room.add_message(message_data) + + for client in room.connected_clients: + if client != ws: + try: + await client.send_json({ + 'action': 'chat_message', + 'broadcast': True, + **message_data + }) + except Exception as e: + logger.error(f"Failed to broadcast to client: {e}") + room.connected_clients.remove(client) + + return { + 'action': 'chat_message', + 'requestId': request_id, + 'success': True, + 'message': message_data + } + + async def handle_join_chat(self, data: dict, ws: web.WebSocketResponse) -> dict: + """Handle a request to join a chat room""" + video_id = data.get('videoId') + request_id = data.get('requestId') + + if not video_id: + return { + 'action': 'join_chat', + 'requestId': request_id, + 'success': False, + 'error': 'No video ID provided' + } + + room = self.chat_rooms[video_id] + room.connected_clients.add(ws) + recent_messages = room.get_recent_messages() + + return { + 'action': 'join_chat', + 'requestId': request_id, + 'success': True, + 'messages': recent_messages + } + + async def handle_leave_chat(self, data: dict, ws: web.WebSocketResponse) -> dict: + """Handle a request to leave a chat room""" + video_id = data.get('videoId') + request_id = data.get('requestId') + + if not video_id: + return { + 'action': 'leave_chat', + 'requestId': request_id, + 'success': False, + 'error': 'No video ID provided' + } + + room = self.chat_rooms[video_id] + if ws in room.connected_clients: + room.connected_clients.remove(ws) + + return { + 'action': 'leave_chat', + 'requestId': request_id, + 'success': True + } \ No newline at end of file diff --git a/docs/for-bots/flutter-videos/fvp.md b/docs/for-bots/flutter-videos/fvp.md new file mode 100644 index 0000000000000000000000000000000000000000..896713065c771506062c4f07d127ff653c226e15 --- /dev/null +++ b/docs/for-bots/flutter-videos/fvp.md @@ -0,0 +1,223 @@ +fvp 0.31.2 ![copy "fvp: ^0.31.2" to clipboard](/static/hash-j60jq2j3/img/content-copy-icon.svg "Copy "fvp: ^0.31.2" to clipboard") + +fvp: ^0.31.2 copied to clipboard + + +====================================================================================================================================================================== + +Published 9 days ago • [![verified publisher](/static/hash-j60jq2j3/img/material-icon-verified.svg "Published by a pub.dev verified publisher")mediadevkit.com](/publishers/mediadevkit.com)Dart 3 compatible + +SDK[Flutter](/packages?q=sdk%3Aflutter "Packages compatible with Flutter SDK") + +Platform[Android](/packages?q=platform%3Aandroid "Packages compatible with Android platform")[iOS](/packages?q=platform%3Aios "Packages compatible with iOS platform")[Linux](/packages?q=platform%3Alinux "Packages compatible with Linux platform")[macOS](/packages?q=platform%3Amacos "Packages compatible with macOS platform")[Windows](/packages?q=platform%3Awindows "Packages compatible with Windows platform") + +![liked status: inactive](/static/hash-j60jq2j3/img/like-inactive.svg)![liked status: active](/static/hash-j60jq2j3/img/like-active.svg)123 + +→ + +### Metadata + +video\_player plugin and backend APIs. Support all desktop/mobile platforms with hardware decoders, optimal renders. Supports most formats via FFmpeg + +More... + +* Readme +* [Changelog](/packages/fvp/changelog) +* [Example](/packages/fvp/example) +* [Installing](/packages/fvp/install) +* [Versions](/packages/fvp/versions) +* [Scores](/packages/fvp/score) + +FVP [#](#fvp) +============= + +A plugin for official [Flutter Video Player](https://pub.dev/packages/video_player) to support all desktop and mobile platforms, with hardware accelerated decoding and optimal rendering. Based on [libmdk](https://github.com/wang-bin/mdk-sdk). You can also create your own players other than official `video_player` with [backend player api](#backend-player-api) + +Prebuilt example can be download from artifacts of [github actions](https://github.com/wang-bin/fvp/actions). + +[More examples are here](https://github.com/wang-bin/mdk-examples/tree/master/flutter) + +project is create with `flutter create -t plugin --platforms=linux,macos,windows,android,ios -i objc -a java fvp` + +Features [#](#features) +----------------------- + +* All platforms: Windows x64(including win7) and arm64, Linux x64 and arm64, macOS, iOS, Android(requires flutter > 3.19 because of minSdk 21). +* You can choose official implementation or this plugin's +* Optimal render api: d3d11 for windows, metal for macOS/iOS, OpenGL for Linux and Android(Impeller support) +* Hardware decoders are enabled by default +* Dolby Vision support on all platforms +* Minimal code change for existing [Video Player](https://pub.dev/packages/video_player) apps +* Support most formats via FFmpeg demuxer and software decoders if not supported by gpu. You can use your own ffmpeg 4.0~7.1(or master branch) by removing bundled ffmpeg dynamic library. +* High performance. Lower cpu, gpu and memory load than libmpv based players. +* Support audio without video +* HEVC, VP8 and VP9 transparent video +* Small footprint. Only about 10MB size increase per cpu architecture(platform dependent). + +How to Use [#](#how-to-use) +--------------------------- + +* Add [fvp](https://pub.dev/packages/fvp) in your pubspec.yaml dependencies: `flutter pub add fvp`. Since flutter 3.27, fvp must be a direct dependency in your app's pubspec.yaml. +* **(Optional)** Add 2 lines in your video\_player examples. Without this step, this plugin will be used for video\_player unsupported platforms(windows, linux), official implementation will be used otherwise. + + import 'package:fvp/fvp.dart' as fvp; + + fvp.registerWith(); // in main() or anywhere before creating a player. use fvp for all platforms. + + +copied to clipboard + +You can also select the platforms to enable fvp implementation + + registerWith(options: {'platforms': ['windows', 'macos', 'linux']}); // only these platforms will use this plugin implementation + + +copied to clipboard + +To select [other decoders](https://github.com/wang-bin/mdk-sdk/wiki/Decoders), pass options like this + + fvp.registerWith(options: { + 'video.decoders': ['D3D11', 'NVDEC', 'FFmpeg'] + //'lowLatency': 1, // optional for network streams + }); // windows + + +copied to clipboard + +[The document](https://pub.dev/documentation/fvp/latest/fvp/registerWith.html) lists all options for `registerWith()` + +### Error Handling [#](#error-handling) + +Errors are usually produced when loading a media. + + _controller.addListener(() { + if (_controller.value.hasError && !_controller.value.isCompleted) { + ... + + +copied to clipboard + +### Backend Player API [#](#backend-player-api) + + import 'package:fvp/mdk.dart'; + + +copied to clipboard + +The plugin implements [VideoPlayerPlatform](https://pub.dev/packages/video_player_platform_interface) via [a thin wrapper](https://github.com/wang-bin/fvp/blob/master/lib/video_player_mdk.dart) on [player.dart](https://github.com/wang-bin/fvp/blob/master/lib/src/player.dart). + +Now we also expose this backend player api so you can create your own players easily, and gain more features than official [video\_player](https://pub.dev/packages/video_player), for example, play from a given position, loop in a range, decoder selection, media information detail etc. You can also reuse the Player instance without unconditionally create and dispose, changing the `Player.media` is enough. [This is an example](https://github.com/wang-bin/mdk-examples/blob/master/flutter/simple/lib/multi_textures.dart) + +### VideoPlayerController Extensions [#](#videoplayercontroller-extensions) + +With this extension, we can leverage mature `video_player` code without rewriting a new one via backend player api, but gain more features, for example `snapshot()`, `record()`, `fastSeekTo()`, `setExternalSubtitle()`. + + import 'package:fvp/fvp.dart' as fvp; + + fvp.registerWith(); // in main() or anywhere before creating a player. use fvp for all platforms. + + // somewhere after controller is initialized + _controller.record('rtmp://127.0.0.1/live/test'); + + +copied to clipboard + +Upgrade Dependencies Manually [#](#upgrade-dependencies-manually) +================================================================= + +Upgrading binary dependencies can bring new features and backend bug fixes. For macOS and iOS, in your project dir, run + + pod cache clean mdk + find . -name Podfile.lock -delete + rm -rf {mac,i}os/Pods + + +copied to clipboard + +For other platforms, set environment var `FVP_DEPS_LATEST=1` and rebuilt, will upgrade to the latest sdk. If fvp is installed from pub.dev, run `flutter pub cache clean` is another option. + +Design [#](#design) +=================== + +* Playback control api in dart via ffi +* Manage video renderers in platform specific manners. Receive player ptr via `MethodChannel` to construct player instance and set a renderer target. +* Callbacks and events in C++ are notified by ReceivePort +* Function with a one time callback is async and returns a future + +Enable Subtitles [#](#enable-subtitles) +======================================= + +libass is required, and it's added to your app automatically for windows, macOS and android(remove ass.dll, libass.dylib and libass.so from mdk-sdk if you don't need it). For iOS, [download](https://sourceforge.net/projects/mdk-sdk/files/deps/dep.7z/download) and add `ass.framework` to your xcode project. For linux, system libass can be used, you may have to install manually via system package manager. + +If required subtitle font is not found in the system(e.g. android), you can add [assets/subfont.ttf](https://github.com/mpv-android/mpv-android/raw/master/app/src/main/assets/subfont.ttf) in pubspec.yaml assets as the fallback. Optionally you can also download the font file by fvp like this + + fvp.registerWith(options: { + 'subtitleFontFile': 'https://github.com/mpv-android/mpv-android/raw/master/app/src/main/assets/subfont.ttf' + }); + + +copied to clipboard + +DO NOT use flutter-snap [#](#do-not-use-flutter-snap) +===================================================== + +Flutter can be installed by snap, but it will add some [enviroment vars(`CPLUS_INCLUDE_PATH` and `LIBRARY_PATH`) which may break C++ compiler](https://github.com/canonical/flutter-snap/blob/main/env.sh#L15-L18). It's not recommended to use snap, althrough building for linux is [fixed](https://github.com/wang-bin/fvp/commit/567c68270ba16b95b1198ae58850707ae4ad7b22), but it's not possible for android. + +Screenshots [#](#screenshots) +============================= + +![fpv_android](https://user-images.githubusercontent.com/785206/248862591-40f458e5-d7ca-4513-b709-b056deaaf421.jpeg) ![fvp_ios](https://user-images.githubusercontent.com/785206/250348936-e5e1fb14-9c81-4652-8f53-37e8d64195a3.jpg) ![fvp_win](https://user-images.githubusercontent.com/785206/248859525-920bdd51-6947-4a00-87b4-9c1a21a68d51.jpeg) ![fvp_win7](https://user-images.githubusercontent.com/785206/266754957-883d05c9-a057-4c1c-b824-0dc385a13f78.jpg) ![fvp_linux](https://user-images.githubusercontent.com/785206/248859533-ce2ad50b-2ead-43bb-bf25-6e2575c5ebe1.jpeg) ![fvp_macos](https://user-images.githubusercontent.com/785206/248859538-71de39a4-c5f0-4c8f-9920-d7dfc6cd0d9a.jpg) + +[ + +123 + +likes + +150 + +points + +4.48k + +downloads + + + +](/packages/fvp/score) + +### Publisher + +[![verified publisher](/static/hash-j60jq2j3/img/material-icon-verified.svg "Published by a pub.dev verified publisher")mediadevkit.com](/publishers/mediadevkit.com) + +### Weekly Downloads + +2024.10.07 - 2025.04.21 + +### Metadata + +video\_player plugin and backend APIs. Support all desktop/mobile platforms with hardware decoders, optimal renders. Supports most formats via FFmpeg + +[Repository (GitHub)](https://github.com/wang-bin/fvp) + +### Topics + +[#video](/packages?q=topic%3Avideo) [#player](/packages?q=topic%3Aplayer) [#video-player](/packages?q=topic%3Avideo-player) [#audio-player](/packages?q=topic%3Aaudio-player) [#videoplayer](/packages?q=topic%3Avideoplayer) + +### Documentation + +[API reference](/documentation/fvp/latest/) + +### License + +![](/static/hash-j60jq2j3/img/material-icon-balance.svg)BSD-3-Clause ([license](/packages/fvp/license)) + +### Dependencies + +[ffi](/packages/ffi "^2.1.0"), [flutter](https://api.flutter.dev/), [http](/packages/http "^1.0.0"), [logging](/packages/logging "^1.2.0"), [path](/packages/path "^1.8.0"), [path\_provider](/packages/path_provider "^2.1.2"), [plugin\_platform\_interface](/packages/plugin_platform_interface "^2.0.0"), [video\_player](/packages/video_player "^2.6.0"), [video\_player\_platform\_interface](/packages/video_player_platform_interface "^6.2.0") + +### More + +[Packages that depend on fvp](/packages?q=dependency%3Afvp) + +{"@context":"http\\u003a\\u002f\\u002fschema.org","@type":"SoftwareSourceCode","name":"fvp","version":"0.31.2","description":"fvp - video\\u005fplayer plugin and backend APIs. Support all desktop\\u002fmobile platforms with hardware decoders, optimal renders. Supports most formats via FFmpeg","url":"https\\u003a\\u002f\\u002fpub.dev\\u002fpackages\\u002ffvp","dateCreated":"2023-06-26T16\\u003a23\\u003a33.605901Z","dateModified":"2025-04-14T04\\u003a14\\u003a34.630262Z","programmingLanguage":"Dart","image":"https\\u003a\\u002f\\u002fpub.dev\\u002fstatic\\u002fimg\\u002fpub-dev-icon-cover-image.png","license":"https\\u003a\\u002f\\u002fpub.dev\\u002fpackages\\u002ffvp\\u002flicense"} \ No newline at end of file diff --git a/docs/for-bots/flutter-videos/fvp_usage_example__main.dart b/docs/for-bots/flutter-videos/fvp_usage_example__main.dart new file mode 100644 index 0000000000000000000000000000000000000000..8cd5743a12af864a9dc65efd7ebcced94ff747e0 --- /dev/null +++ b/docs/for-bots/flutter-videos/fvp_usage_example__main.dart @@ -0,0 +1,369 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file is used to extract code samples for the README.md file. +// Run update-excerpts if you modify this file. + +// ignore_for_file: library_private_types_in_public_api, public_member_api_docs + +// #docregion basic-example +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:video_player/video_player.dart'; +import 'package:fvp/fvp.dart'; +import 'package:logging/logging.dart'; +import 'package:intl/intl.dart'; +import 'package:file_selector/file_selector.dart'; +import 'package:image/image.dart' as img; + +String? source; + +void main(List args) async { +// set logger before registerWith() + Logger.root.level = Level.ALL; + final df = DateFormat("HH:mm:ss.SSS"); + Logger.root.onRecord.listen((record) { + debugPrint( + '${record.loggerName}.${record.level.name}: ${df.format(record.time)}: ${record.message}', + wrapWidth: 0x7FFFFFFFFFFFFFFF); + }); + + final opts = {}; + final globalOpts = {}; + int i = 0; + bool useFvp = true; + opts['subtitleFontFile'] = + 'https://github.com/mpv-android/mpv-android/raw/master/app/src/main/assets/subfont.ttf'; + for (; i < args.length; i++) { + if (args[i] == '-c:v') { + opts['video.decoders'] = [args[++i]]; + } else if (args[i] == '-maxSize') { + // ${w}x${h} + final size = args[++i].split('x'); + opts['maxWidth'] = int.parse(size[0]); + opts['maxHeight'] = int.parse(size[1]); + } else if (args[i] == '-fvp') { + useFvp = int.parse(args[++i]) > 0; + } else if (args[i].startsWith('-')) { + globalOpts[args[i].substring(1)] = args[++i]; + } else { + break; + } + } + if (globalOpts.isNotEmpty) { + opts['global'] = globalOpts; + } + opts['lowLatency'] = 0; + + if (i <= args.length - 1) source = args[args.length - 1]; + source ??= await getStartFile(); + + if (useFvp) { + registerWith(options: opts); + } + + runApp(const MaterialApp( + localizationsDelegates: [DefaultMaterialLocalizations.delegate], + title: 'Video Demo', + home: VideoApp())); +} + +Future getStartFile() async { + if (Platform.isMacOS) { + WidgetsFlutterBinding.ensureInitialized(); + const hostApi = MethodChannel("openFile"); + return await hostApi.invokeMethod("getCurrentFile"); + } + return null; +} + +Future showURIPicker(BuildContext context) async { + final key = GlobalKey(); + final src = TextEditingController(); + String? uri; + await showModalBottomSheet( + context: context, + builder: (context) => Container( + alignment: Alignment.center, + child: Form( + key: key, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + TextFormField( + controller: src, + style: const TextStyle(fontSize: 14.0), + decoration: const InputDecoration( + border: UnderlineInputBorder(), + labelText: 'Video URI', + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter a URI'; + } + return null; + }, + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: ElevatedButton( + onPressed: () { + if (key.currentState!.validate()) { + uri = src.text; + Navigator.of(context).maybePop(); + } + }, + child: const Text('Play'), + ), + ), + ], + ), + ), + ), + ), + ); + return uri; +} + +/// Stateful widget to fetch and then display video content. +class VideoApp extends StatefulWidget { + const VideoApp({super.key}); + + @override + _VideoAppState createState() => _VideoAppState(); +} + +class _VideoAppState extends State { + late VideoPlayerController _controller; + + @override + void initState() { + super.initState(); + if (source != null) { + if (File(source!).existsSync()) { + playFile(source!); + } else { + playUri(source!); + } + } else { + playUri( + 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4'); + } + } + + void playFile(String path) { + _controller.dispose(); + _controller = VideoPlayerController.file(File(path)); + _controller.addListener(() { + setState(() {}); + }); + _controller.initialize().then((_) { + // Ensure the first frame is shown after the video is initialized, even before the play button has been pressed. + setState(() {}); + _controller.play(); + }); + } + + void playUri(String uri) { + _controller = VideoPlayerController.network(uri); + _controller.addListener(() { + setState(() {}); + }); + _controller.initialize().then((_) { + // Ensure the first frame is shown after the video is initialized, even before the play button has been pressed. + setState(() {}); + _controller.play(); + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + floatingActionButton: Row( + children: [ + FloatingActionButton( + heroTag: 'file', + tooltip: 'Open [File]', + onPressed: () async { + const XTypeGroup typeGroup = XTypeGroup( + label: 'videos', + //extensions: ['*'], + ); + final XFile? file = + await openFile(acceptedTypeGroups: [typeGroup]); + + if (file != null) { + playFile(file.path); + } + }, + child: const Icon(Icons.file_open), + ), + const SizedBox(width: 16.0), + FloatingActionButton( + heroTag: 'uri', + tooltip: 'Open [Uri]', + onPressed: () { + showURIPicker(context).then((value) { + if (value != null) { + playUri(value); + } + }); + }, + child: const Icon(Icons.link), + ), + const SizedBox(width: 16.0), + FloatingActionButton( + heroTag: 'snapshot', + tooltip: 'Snapshot', + onPressed: () { + if (_controller.value.isInitialized) { + final info = _controller.getMediaInfo()?.video?[0].codec; + if (info == null) { + debugPrint('No video codec info'); + return; + } + final width = info.width; + final height = info.height; + _controller.snapshot().then((value) { + if (value != null) { + // value is rgba data, must encode to png image and save as a file + final i = img.Image.fromBytes( + width: width, + height: height, + bytes: value.buffer, + numChannels: 4, + rowStride: width * 4); + final savePath = + '${Directory.systemTemp.path}/snapshot.jpg'; + img.encodeJpgFile(savePath, i, quality: 70).then((value) { + final msg = value + ? 'Snapshot saved to $savePath' + : 'Failed to save snapshot'; + debugPrint(msg); + // show a toast + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(msg), + duration: const Duration(seconds: 2), + ), + ); + } + }); + } + }); + } + }, + child: const Icon(Icons.screenshot), + ), + ], + ), + body: Center( + child: _controller.value.isInitialized + ? AspectRatio( + aspectRatio: _controller.value.aspectRatio, + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + VideoPlayer(_controller), + _ControlsOverlay(controller: _controller), + VideoProgressIndicator(_controller, allowScrubbing: true), + ], + ), + ) + : Container(), + ), + ); + } + + @override + void dispose() { + super.dispose(); + _controller.dispose(); + } +} + +class _ControlsOverlay extends StatelessWidget { + const _ControlsOverlay({required this.controller}); + + static const List _examplePlaybackRates = [ + 0.25, + 0.5, + 1.0, + 1.5, + 2.0, + 3.0, + 5.0, + 10.0, + ]; + + final VideoPlayerController controller; + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + AnimatedSwitcher( + duration: const Duration(milliseconds: 50), + reverseDuration: const Duration(milliseconds: 200), + child: controller.value.isPlaying + ? const SizedBox.shrink() + : Container( + color: Colors.black26, + child: const Center( + child: Icon( + Icons.play_arrow, + color: Colors.white, + size: 100.0, + semanticLabel: 'Play', + ), + ), + ), + ), + GestureDetector( + onTap: () { + controller.value.isPlaying ? controller.pause() : controller.play(); + }, + ), + Align( + alignment: Alignment.topRight, + child: PopupMenuButton( + initialValue: controller.value.playbackSpeed, + tooltip: 'Playback speed', + onSelected: (double speed) { + controller.setPlaybackSpeed(speed); + }, + itemBuilder: (BuildContext context) { + return >[ + for (final double speed in _examplePlaybackRates) + PopupMenuItem( + value: speed, + child: Text('${speed}x'), + ) + ]; + }, + child: Padding( + padding: const EdgeInsets.symmetric( + // Using less vertical padding as the text is also longer + // horizontally, so it feels like it would need more spacing + // horizontally (matching the aspect ratio of the video). + vertical: 12, + horizontal: 16, + ), + child: Text('${controller.value.playbackSpeed}x'), + ), + ), + ), + ], + ); + } +} + +// #enddocregion basic-example diff --git a/docs/for-bots/flutter-videos/fvp_usage_example__multi_textures.dart b/docs/for-bots/flutter-videos/fvp_usage_example__multi_textures.dart new file mode 100644 index 0000000000000000000000000000000000000000..9e4c00c7736dbf1d25c20146ed73200f284be4b4 --- /dev/null +++ b/docs/for-bots/flutter-videos/fvp_usage_example__multi_textures.dart @@ -0,0 +1,116 @@ +import 'package:flutter/material.dart'; +import 'package:fvp/mdk.dart'; + +void main(List args) async { + runApp(const SinglePlayerMultipleVideoWidget()); +} +class SinglePlayerMultipleVideoWidget extends StatefulWidget { + const SinglePlayerMultipleVideoWidget({Key? key}) : super(key: key); + + @override + State createState() => + _SinglePlayerMultipleVideoWidgetState(); +} + +class _SinglePlayerMultipleVideoWidgetState + extends State { + late final player = Player(); + + @override + void initState() { + super.initState(); + + player.media = 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4'; + player.loop = -1; + player.state = PlaybackState.playing; + player.updateTexture(); + } + + @override + void dispose() { + player.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'package:fvp', + home: Scaffold( + body: Center( + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + flex: 3, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: Card( + elevation: 8.0, + color: Colors.black, + clipBehavior: Clip.antiAlias, + margin: const EdgeInsets.all(32.0), + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Expanded( + child: Row( + children: [ + Expanded( + child: ValueListenableBuilder( + valueListenable: player.textureId, + builder: (context, id, _) => id == null + ? const SizedBox.shrink() + : Texture(textureId: id), + ), + ), + Expanded( + child: ValueListenableBuilder( + valueListenable: player.textureId, + builder: (context, id, _) => id == null + ? const SizedBox.shrink() + : Texture(textureId: id), + ), + ), + ], + ), + ), + Expanded( + child: Row( + children: [ + Expanded( + child: ValueListenableBuilder( + valueListenable: player.textureId, + builder: (context, id, _) => id == null + ? const SizedBox.shrink() + : Texture(textureId: id), + ), + ), + Expanded( + child: ValueListenableBuilder( + valueListenable: player.textureId, + builder: (context, id, _) => id == null + ? const SizedBox.shrink() + : Texture(textureId: id), + ), + ), + ], + ), + ), + ], + ), + ), + ), + const SizedBox(height: 32.0), + ], + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/docs/for-bots/flutter-videos/video_player.md b/docs/for-bots/flutter-videos/video_player.md new file mode 100644 index 0000000000000000000000000000000000000000..9b42ec5f1a22e017702807d25d1abab94973b4ca --- /dev/null +++ b/docs/for-bots/flutter-videos/video_player.md @@ -0,0 +1,145 @@ +Video Player plugin for Flutter [#](#video-player-plugin-for-flutter) +===================================================================== + +[![pub package](https://img.shields.io/pub/v/video_player.svg)](https://pub.dev/packages/video_player) + +A Flutter plugin for iOS, Android and Web for playing back video on a Widget surface. + +Android + +iOS + +macOS + +Web + +**Support** + +SDK 16+ + +12.0+ + +10.14+ + +Any\* + +![The example app running in iOS](https://github.com/flutter/packages/blob/main/packages/video_player/video_player/doc/demo_ipod.gif?raw=true) + +Setup [#](#setup) +----------------- + +### iOS [#](#ios) + +If you need to access videos using `http` (rather than `https`) URLs, you will need to add the appropriate `NSAppTransportSecurity` permissions to your app's _Info.plist_ file, located in `/ios/Runner/Info.plist`. See [Apple's documentation](https://developer.apple.com/documentation/bundleresources/information_property_list/nsapptransportsecurity) to determine the right combination of entries for your use case and supported iOS versions. + +### Android [#](#android) + +If you are using network-based videos, ensure that the following permission is present in your Android Manifest file, located in `/android/app/src/main/AndroidManifest.xml`: + + + + +copied to clipboard + +### macOS [#](#macos) + +If you are using network-based videos, you will need to [add the `com.apple.security.network.client` entitlement](https://flutter.dev/to/macos-entitlements) + +### Web [#](#web) + +> The Web platform does **not** support `dart:io`, so avoid using the `VideoPlayerController.file` constructor for the plugin. Using the constructor attempts to create a `VideoPlayerController.file` that will throw an `UnimplementedError`. + +\* Different web browsers may have different video-playback capabilities (supported formats, autoplay...). Check [package:video\_player\_web](https://pub.dev/packages/video_player_web) for more web-specific information. + +The `VideoPlayerOptions.mixWithOthers` option can't be implemented in web, at least at the moment. If you use this option in web it will be silently ignored. + +Supported Formats [#](#supported-formats) +----------------------------------------- + +* On iOS and macOS, the backing player is [AVPlayer](https://developer.apple.com/documentation/avfoundation/avplayer). The supported formats vary depending on the version of iOS, [AVURLAsset](https://developer.apple.com/documentation/avfoundation/avurlasset) class has [audiovisualTypes](https://developer.apple.com/documentation/avfoundation/avurlasset/1386800-audiovisualtypes?language=objc) that you can query for supported av formats. +* On Android, the backing player is [ExoPlayer](https://google.github.io/ExoPlayer/), please refer [here](https://google.github.io/ExoPlayer/supported-formats.html) for list of supported formats. +* On Web, available formats depend on your users' browsers (vendor and version). Check [package:video\_player\_web](https://pub.dev/packages/video_player_web) for more specific information. + +Example [#](#example) +--------------------- + + import 'package:flutter/material.dart'; + import 'package:video_player/video_player.dart'; + + void main() => runApp(const VideoApp()); + + /// Stateful widget to fetch and then display video content. + class VideoApp extends StatefulWidget { + const VideoApp({super.key}); + + @override + _VideoAppState createState() => _VideoAppState(); + } + + class _VideoAppState extends State { + late VideoPlayerController _controller; + + @override + void initState() { + super.initState(); + _controller = VideoPlayerController.networkUrl(Uri.parse( + 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4')) + ..initialize().then((_) { + // Ensure the first frame is shown after the video is initialized, even before the play button has been pressed. + setState(() {}); + }); + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Video Demo', + home: Scaffold( + body: Center( + child: _controller.value.isInitialized + ? AspectRatio( + aspectRatio: _controller.value.aspectRatio, + child: VideoPlayer(_controller), + ) + : Container(), + ), + floatingActionButton: FloatingActionButton( + onPressed: () { + setState(() { + _controller.value.isPlaying + ? _controller.pause() + : _controller.play(); + }); + }, + child: Icon( + _controller.value.isPlaying ? Icons.pause : Icons.play_arrow, + ), + ), + ), + ); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + } + + +copied to clipboard + +Usage [#](#usage) +----------------- + +The following section contains usage information that goes beyond what is included in the documentation in order to give a more elaborate overview of the API. + +This is not complete as of now. You can contribute to this section by [opening a pull request](https://github.com/flutter/packages/pulls). + +### Playback speed [#](#playback-speed) + +You can set the playback speed on your `_controller` (instance of `VideoPlayerController`) by calling `_controller.setPlaybackSpeed`. `setPlaybackSpeed` takes a `double` speed value indicating the rate of playback for your video. For example, when given a value of `2.0`, your video will play at 2x the regular playback speed and so on. + +To learn about playback speed limitations, see the [`setPlaybackSpeed` method documentation](https://pub.dev/documentation/video_player/latest/video_player/VideoPlayerController/setPlaybackSpeed.html). + +Furthermore, see the example app for an example playback speed implementation. \ No newline at end of file diff --git a/docs/for-bots/huggingface/hf-api.md b/docs/for-bots/huggingface/hf-api.md new file mode 100644 index 0000000000000000000000000000000000000000..b8dde469d4cd8b56450281855f36951505cc45bc --- /dev/null +++ b/docs/for-bots/huggingface/hf-api.md @@ -0,0 +1,5122 @@ +[](#hfapi-client)HfApi Client +============================= + +Below is the documentation for the `HfApi` class, which serves as a Python wrapper for the Hugging Face Hub’s API. + +All methods from the `HfApi` are also accessible from the package’s root directly. Both approaches are detailed below. + +Using the root method is more straightforward but the [HfApi](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi) class gives you more flexibility. In particular, you can pass a token that will be reused in all HTTP calls. This is different than `huggingface-cli login` or [login()](/docs/huggingface_hub/v0.30.2/en/package_reference/authentication#huggingface_hub.login) as the token is not persisted on the machine. It is also possible to provide a different endpoint or configure a custom user-agent. + +Copied + +from huggingface\_hub import HfApi, list\_models + +\# Use root method +models = list\_models() + +\# Or configure a HfApi client +hf\_api = HfApi( + endpoint="https://huggingface.co", \# Can be a Private Hub endpoint. + token="hf\_xxx", \# Token is not persisted on the machine. +) +models = hf\_api.list\_models() + +[](#huggingface_hub.HfApi)HfApi +------------------------------- + +### class huggingface\_hub.HfApi + +[](#huggingface_hub.HfApi)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L1630) + +( endpoint: Optional\[str\] = Nonetoken: Union\[str, bool, None\] = Nonelibrary\_name: Optional\[str\] = Nonelibrary\_version: Optional\[str\] = Noneuser\_agent: Union\[Dict, str, None\] = Noneheaders: Optional\[Dict\[str, str\]\] = None ) + +Parameters + +* [](#huggingface_hub.HfApi.endpoint)**endpoint** (`str`, _optional_) — Endpoint of the Hub. Defaults to [https://huggingface.co](https://huggingface.co). +* [](#huggingface_hub.HfApi.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. +* [](#huggingface_hub.HfApi.library_name)**library\_name** (`str`, _optional_) — The name of the library that is making the HTTP request. Will be added to the user-agent header. Example: `"transformers"`. +* [](#huggingface_hub.HfApi.library_version)**library\_version** (`str`, _optional_) — The version of the library that is making the HTTP request. Will be added to the user-agent header. Example: `"4.24.0"`. +* [](#huggingface_hub.HfApi.user_agent)**user\_agent** (`str`, `dict`, _optional_) — The user agent info in the form of a dictionary or a single string. It will be completed with information about the installed packages. +* [](#huggingface_hub.HfApi.headers)**headers** (`dict`, _optional_) — Additional headers to be sent with each request. Example: `{"X-My-Header": "value"}`. Headers passed here are taking precedence over the default headers. + +Client to interact with the Hugging Face Hub via HTTP. + +The client is initialized with some high-level settings used in all requests made to the Hub (HF endpoint, authentication, user agents…). Using the `HfApi` client is preferred but not mandatory as all of its public methods are exposed directly at the root of `huggingface_hub`. + +#### accept\_access\_request + +[](#huggingface_hub.HfApi.accept_access_request)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L8839) + +( repo\_id: struser: strrepo\_type: Optional\[str\] = Nonetoken: Union\[bool, str, None\] = None ) + +Expand 4 parameters + +Parameters + +* [](#huggingface_hub.HfApi.accept_access_request.repo_id)**repo\_id** (`str`) — The id of the repo to accept access request for. +* [](#huggingface_hub.HfApi.accept_access_request.user)**user** (`str`) — The username of the user which access request should be accepted. +* [](#huggingface_hub.HfApi.accept_access_request.repo_type)**repo\_type** (`str`, _optional_) — The type of the repo to accept access request for. Must be one of `model`, `dataset` or `space`. Defaults to `model`. +* [](#huggingface_hub.HfApi.accept_access_request.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Raises + +export const metadata = 'undefined'; + +`HTTPError` + +export const metadata = 'undefined'; + +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) — HTTP 400 if the repo is not gated. +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) — HTTP 403 if you only have read-only access to the repo. This can be the case if you don’t have `write` or `admin` role in the organization the repo belongs to or if you passed a `read` token. +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) — HTTP 404 if the user does not exist on the Hub. +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) — HTTP 404 if the user access request cannot be found. +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) — HTTP 404 if the user access request is already in the accepted list. + +Accept an access request from a user for a given gated repo. + +Once the request is accepted, the user will be able to download any file of the repo and access the community tab. If the approval mode is automatic, you don’t have to accept requests manually. An accepted request can be cancelled or rejected at any time using [cancel\_access\_request()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.cancel_access_request) and [reject\_access\_request()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.reject_access_request). + +For more info about gated repos, see [https://huggingface.co/docs/hub/models-gated](https://huggingface.co/docs/hub/models-gated). + +#### add\_collection\_item + +[](#huggingface_hub.HfApi.add_collection_item)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L8391) + +( collection\_slug: stritem\_id: stritem\_type: CollectionItemType\_Tnote: Optional\[str\] = Noneexists\_ok: bool = Falsetoken: Union\[bool, str, None\] = None ) + +Expand 6 parameters + +Parameters + +* [](#huggingface_hub.HfApi.add_collection_item.collection_slug)**collection\_slug** (`str`) — Slug of the collection to update. Example: `"TheBloke/recent-models-64f9a55bb3115b4f513ec026"`. +* [](#huggingface_hub.HfApi.add_collection_item.item_id)**item\_id** (`str`) — ID of the item to add to the collection. It can be the ID of a repo on the Hub (e.g. `"facebook/bart-large-mnli"`) or a paper id (e.g. `"2307.09288"`). +* [](#huggingface_hub.HfApi.add_collection_item.item_type)**item\_type** (`str`) — Type of the item to add. Can be one of `"model"`, `"dataset"`, `"space"` or `"paper"`. +* [](#huggingface_hub.HfApi.add_collection_item.note)**note** (`str`, _optional_) — A note to attach to the item in the collection. The maximum size for a note is 500 characters. +* [](#huggingface_hub.HfApi.add_collection_item.exists_ok)**exists\_ok** (`bool`, _optional_) — If `True`, do not raise an error if item already exists. +* [](#huggingface_hub.HfApi.add_collection_item.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Raises + +export const metadata = 'undefined'; + +`HTTPError` + +export const metadata = 'undefined'; + +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) — HTTP 403 if you only have read-only access to the repo. This can be the case if you don’t have `write` or `admin` role in the organization the repo belongs to or if you passed a `read` token. +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) — HTTP 404 if the item you try to add to the collection does not exist on the Hub. +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) — HTTP 409 if the item you try to add to the collection is already in the collection (and exists\_ok=False) + +Add an item to a collection on the Hub. + +Returns: [Collection](/docs/huggingface_hub/v0.30.2/en/package_reference/collections#huggingface_hub.Collection) + +[](#huggingface_hub.HfApi.add_collection_item.example) + +Example: + +Copied + +\>>> from huggingface\_hub import add\_collection\_item +\>>> collection = add\_collection\_item( +... collection\_slug="davanstrien/climate-64f99dc2a5067f6b65531bab", +... item\_id="pierre-loic/climate-news-articles", +... item\_type="dataset" +... ) +\>>> collection.items\[-1\].item\_id +"pierre-loic/climate-news-articles" +\# ^item got added to the collection on last position + +\# Add item with a note +\>>> add\_collection\_item( +... collection\_slug="davanstrien/climate-64f99dc2a5067f6b65531bab", +... item\_id="datasets/climate\_fever", +... item\_type="dataset" +... note="This dataset adopts the FEVER methodology that consists of 1,535 real-world claims regarding climate-change collected on the internet." +... ) +(...) + +#### add\_space\_secret + +[](#huggingface_hub.HfApi.add_space_secret)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L6932) + +( repo\_id: strkey: strvalue: strdescription: Optional\[str\] = Nonetoken: Union\[bool, str, None\] = None ) + +Parameters + +* [](#huggingface_hub.HfApi.add_space_secret.repo_id)**repo\_id** (`str`) — ID of the repo to update. Example: `"bigcode/in-the-stack"`. +* [](#huggingface_hub.HfApi.add_space_secret.key)**key** (`str`) — Secret key. Example: `"GITHUB_API_KEY"` +* [](#huggingface_hub.HfApi.add_space_secret.value)**value** (`str`) — Secret value. Example: `"your_github_api_key"`. +* [](#huggingface_hub.HfApi.add_space_secret.description)**description** (`str`, _optional_) — Secret description. Example: `"Github API key to access the Github API"`. +* [](#huggingface_hub.HfApi.add_space_secret.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Adds or updates a secret in a Space. + +Secrets allow to set secret keys or tokens to a Space without hardcoding them. For more details, see [https://huggingface.co/docs/hub/spaces-overview#managing-secrets](https://huggingface.co/docs/hub/spaces-overview#managing-secrets). + +#### add\_space\_variable + +[](#huggingface_hub.HfApi.add_space_variable)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L7020) + +( repo\_id: strkey: strvalue: strdescription: Optional\[str\] = Nonetoken: Union\[bool, str, None\] = None ) + +Parameters + +* [](#huggingface_hub.HfApi.add_space_variable.repo_id)**repo\_id** (`str`) — ID of the repo to update. Example: `"bigcode/in-the-stack"`. +* [](#huggingface_hub.HfApi.add_space_variable.key)**key** (`str`) — Variable key. Example: `"MODEL_REPO_ID"` +* [](#huggingface_hub.HfApi.add_space_variable.value)**value** (`str`) — Variable value. Example: `"the_model_repo_id"`. +* [](#huggingface_hub.HfApi.add_space_variable.description)**description** (`str`) — Description of the variable. Example: `"Model Repo ID of the implemented model"`. +* [](#huggingface_hub.HfApi.add_space_variable.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Adds or updates a variable in a Space. + +Variables allow to set environment variables to a Space without hardcoding them. For more details, see [https://huggingface.co/docs/hub/spaces-overview#managing-secrets-and-environment-variables](https://huggingface.co/docs/hub/spaces-overview#managing-secrets-and-environment-variables) + +#### auth\_check + +[](#huggingface_hub.HfApi.auth_check)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L9747) + +( repo\_id: strrepo\_type: Optional\[str\] = Nonetoken: Union\[bool, str, None\] = None ) + +Parameters + +* [](#huggingface_hub.HfApi.auth_check.repo_id)**repo\_id** (`str`) — The repository to check for access. Format should be `"user/repo_name"`. Example: `"user/my-cool-model"`. +* [](#huggingface_hub.HfApi.auth_check.repo_type)**repo\_type** (`str`, _optional_) — The type of the repository. Should be one of `"model"`, `"dataset"`, or `"space"`. If not specified, the default is `"model"`. +* [](#huggingface_hub.HfApi.auth_check.token)**token** `(Union[bool, str, None]`, _optional_) — A valid user access token. If not provided, the locally saved token will be used, which is the recommended authentication method. Set to `False` to disable authentication. Refer to: [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication). + +Raises + +export const metadata = 'undefined'; + +[RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) or [GatedRepoError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.GatedRepoError) + +export const metadata = 'undefined'; + +* [RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) — Raised if the repository does not exist, is private, or the user does not have access. This can occur if the `repo_id` or `repo_type` is incorrect or if the repository is private but the user is not authenticated. + +* [GatedRepoError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.GatedRepoError) — Raised if the repository exists but is gated and the user is not authorized to access it. + + +Check if the provided user token has access to a specific repository on the Hugging Face Hub. + +This method verifies whether the user, authenticated via the provided token, has access to the specified repository. If the repository is not found or if the user lacks the required permissions to access it, the method raises an appropriate exception. + +Example: + +[](#huggingface_hub.HfApi.auth_check.example) + +Check if the user has access to a repository: + +Copied + +\>>> from huggingface\_hub import auth\_check +\>>> from huggingface\_hub.utils import GatedRepoError, RepositoryNotFoundError + +try: + auth\_check("user/my-cool-model") +except GatedRepoError: + \# Handle gated repository error + print("You do not have permission to access this gated repository.") +except RepositoryNotFoundError: + \# Handle repository not found error + print("The repository was not found or you do not have access.") + +In this example: + +* If the user has access, the method completes successfully. +* If the repository is gated or does not exist, appropriate exceptions are raised, allowing the user to handle them accordingly. + +#### cancel\_access\_request + +[](#huggingface_hub.HfApi.cancel_access_request)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L8799) + +( repo\_id: struser: strrepo\_type: Optional\[str\] = Nonetoken: Union\[bool, str, None\] = None ) + +Expand 4 parameters + +Parameters + +* [](#huggingface_hub.HfApi.cancel_access_request.repo_id)**repo\_id** (`str`) — The id of the repo to cancel access request for. +* [](#huggingface_hub.HfApi.cancel_access_request.user)**user** (`str`) — The username of the user which access request should be cancelled. +* [](#huggingface_hub.HfApi.cancel_access_request.repo_type)**repo\_type** (`str`, _optional_) — The type of the repo to cancel access request for. Must be one of `model`, `dataset` or `space`. Defaults to `model`. +* [](#huggingface_hub.HfApi.cancel_access_request.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Raises + +export const metadata = 'undefined'; + +`HTTPError` + +export const metadata = 'undefined'; + +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) — HTTP 400 if the repo is not gated. +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) — HTTP 403 if you only have read-only access to the repo. This can be the case if you don’t have `write` or `admin` role in the organization the repo belongs to or if you passed a `read` token. +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) — HTTP 404 if the user does not exist on the Hub. +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) — HTTP 404 if the user access request cannot be found. +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) — HTTP 404 if the user access request is already in the pending list. + +Cancel an access request from a user for a given gated repo. + +A cancelled request will go back to the pending list and the user will lose access to the repo. + +For more info about gated repos, see [https://huggingface.co/docs/hub/models-gated](https://huggingface.co/docs/hub/models-gated). + +#### change\_discussion\_status + +[](#huggingface_hub.HfApi.change_discussion_status)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L6674) + +( repo\_id: strdiscussion\_num: intnew\_status: Literal\['open', 'closed'\]token: Union\[bool, str, None\] = Nonecomment: Optional\[str\] = Nonerepo\_type: Optional\[str\] = None ) → export const metadata = 'undefined';[DiscussionStatusChange](/docs/huggingface_hub/v0.30.2/en/package_reference/community#huggingface_hub.DiscussionStatusChange) + +Parameters + +* [](#huggingface_hub.HfApi.change_discussion_status.repo_id)**repo\_id** (`str`) — A namespace (user or an organization) and a repo name separated by a `/`. +* [](#huggingface_hub.HfApi.change_discussion_status.discussion_num)**discussion\_num** (`int`) — The number of the Discussion or Pull Request . Must be a strictly positive integer. +* [](#huggingface_hub.HfApi.change_discussion_status.new_status)**new\_status** (`str`) — The new status for the discussion, either `"open"` or `"closed"`. +* [](#huggingface_hub.HfApi.change_discussion_status.comment)**comment** (`str`, _optional_) — An optional comment to post with the status change. +* [](#huggingface_hub.HfApi.change_discussion_status.repo_type)**repo\_type** (`str`, _optional_) — Set to `"dataset"` or `"space"` if uploading to a dataset or space, `None` or `"model"` if uploading to a model. Default is `None`. +* [](#huggingface_hub.HfApi.change_discussion_status.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +[DiscussionStatusChange](/docs/huggingface_hub/v0.30.2/en/package_reference/community#huggingface_hub.DiscussionStatusChange) + +export const metadata = 'undefined'; + +the status change event + +Closes or re-opens a Discussion or Pull Request. + +[](#huggingface_hub.HfApi.change_discussion_status.example) + +Examples: + +Copied + +\>>> new\_title = "New title, fixing a typo" +\>>> HfApi().rename\_discussion( +... repo\_id="username/repo\_name", +... discussion\_num=34 +... new\_title=new\_title +... ) +\# DiscussionStatusChange(id='deadbeef0000000', type='status-change', ...) + +Raises the following errors: + +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) if the HuggingFace API returned an error +* [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError) if some parameter value is invalid +* [RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) If the repository to download from cannot be found. This may be because it doesn’t exist, or because it is set to `private` and you do not have access. + +#### comment\_discussion + +[](#huggingface_hub.HfApi.comment_discussion)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L6525) + +( repo\_id: strdiscussion\_num: intcomment: strtoken: Union\[bool, str, None\] = Nonerepo\_type: Optional\[str\] = None ) → export const metadata = 'undefined';[DiscussionComment](/docs/huggingface_hub/v0.30.2/en/package_reference/community#huggingface_hub.DiscussionComment) + +Parameters + +* [](#huggingface_hub.HfApi.comment_discussion.repo_id)**repo\_id** (`str`) — A namespace (user or an organization) and a repo name separated by a `/`. +* [](#huggingface_hub.HfApi.comment_discussion.discussion_num)**discussion\_num** (`int`) — The number of the Discussion or Pull Request . Must be a strictly positive integer. +* [](#huggingface_hub.HfApi.comment_discussion.comment)**comment** (`str`) — The content of the comment to create. Comments support markdown formatting. +* [](#huggingface_hub.HfApi.comment_discussion.repo_type)**repo\_type** (`str`, _optional_) — Set to `"dataset"` or `"space"` if uploading to a dataset or space, `None` or `"model"` if uploading to a model. Default is `None`. +* [](#huggingface_hub.HfApi.comment_discussion.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +[DiscussionComment](/docs/huggingface_hub/v0.30.2/en/package_reference/community#huggingface_hub.DiscussionComment) + +export const metadata = 'undefined'; + +the newly created comment + +Creates a new comment on the given Discussion. + +[](#huggingface_hub.HfApi.comment_discussion.example) + +Examples: + +Copied + +\>>> comment = """ +... Hello @otheruser! +... +... \# This is a title +... +... \*\*This is bold\*\*, \*this is italic\* and ~this is strikethrough~ +... And \[this\](http://url) is a link +... """ + +\>>> HfApi().comment\_discussion( +... repo\_id="username/repo\_name", +... discussion\_num=34 +... comment=comment +... ) +\# DiscussionComment(id='deadbeef0000000', type='comment', ...) + +Raises the following errors: + +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) if the HuggingFace API returned an error +* [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError) if some parameter value is invalid +* [RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) If the repository to download from cannot be found. This may be because it doesn’t exist, or because it is set to `private` and you do not have access. + +#### create\_branch + +[](#huggingface_hub.HfApi.create_branch)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L5866) + +( repo\_id: strbranch: strrevision: Optional\[str\] = Nonetoken: Union\[bool, str, None\] = Nonerepo\_type: Optional\[str\] = Noneexist\_ok: bool = False ) + +Expand 6 parameters + +Parameters + +* [](#huggingface_hub.HfApi.create_branch.repo_id)**repo\_id** (`str`) — The repository in which the branch will be created. Example: `"user/my-cool-model"`. +* [](#huggingface_hub.HfApi.create_branch.branch)**branch** (`str`) — The name of the branch to create. +* [](#huggingface_hub.HfApi.create_branch.revision)**revision** (`str`, _optional_) — The git revision to create the branch from. It can be a branch name or the OID/SHA of a commit, as a hexadecimal string. Defaults to the head of the `"main"` branch. +* [](#huggingface_hub.HfApi.create_branch.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. +* [](#huggingface_hub.HfApi.create_branch.repo_type)**repo\_type** (`str`, _optional_) — Set to `"dataset"` or `"space"` if creating a branch on a dataset or space, `None` or `"model"` if tagging a model. Default is `None`. +* [](#huggingface_hub.HfApi.create_branch.exist_ok)**exist\_ok** (`bool`, _optional_, defaults to `False`) — If `True`, do not raise an error if branch already exists. + +Raises + +export const metadata = 'undefined'; + +[RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) or [BadRequestError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.BadRequestError) or [HfHubHTTPError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.HfHubHTTPError) + +export const metadata = 'undefined'; + +* [RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) — If repository is not found (error 404): wrong repo\_id/repo\_type, private but not authenticated or repo does not exist. +* [BadRequestError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.BadRequestError) — If invalid reference for a branch. Ex: `refs/pr/5` or ‘refs/foo/bar’. +* [HfHubHTTPError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.HfHubHTTPError) — If the branch already exists on the repo (error 409) and `exist_ok` is set to `False`. + +Create a new branch for a repo on the Hub, starting from the specified revision (defaults to `main`). To find a revision suiting your needs, you can use [list\_repo\_refs()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.list_repo_refs) or [list\_repo\_commits()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.list_repo_commits). + +#### create\_collection + +[](#huggingface_hub.HfApi.create_collection)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L8214) + +( title: strnamespace: Optional\[str\] = Nonedescription: Optional\[str\] = Noneprivate: bool = Falseexists\_ok: bool = Falsetoken: Union\[bool, str, None\] = None ) + +Parameters + +* [](#huggingface_hub.HfApi.create_collection.title)**title** (`str`) — Title of the collection to create. Example: `"Recent models"`. +* [](#huggingface_hub.HfApi.create_collection.namespace)**namespace** (`str`, _optional_) — Namespace of the collection to create (username or org). Will default to the owner name. +* [](#huggingface_hub.HfApi.create_collection.description)**description** (`str`, _optional_) — Description of the collection to create. +* [](#huggingface_hub.HfApi.create_collection.private)**private** (`bool`, _optional_) — Whether the collection should be private or not. Defaults to `False` (i.e. public collection). +* [](#huggingface_hub.HfApi.create_collection.exists_ok)**exists\_ok** (`bool`, _optional_) — If `True`, do not raise an error if collection already exists. +* [](#huggingface_hub.HfApi.create_collection.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Create a new Collection on the Hub. + +Returns: [Collection](/docs/huggingface_hub/v0.30.2/en/package_reference/collections#huggingface_hub.Collection) + +[](#huggingface_hub.HfApi.create_collection.example) + +Example: + +Copied + +\>>> from huggingface\_hub import create\_collection +\>>> collection = create\_collection( +... title="ICCV 2023", +... description="Portfolio of models, papers and demos I presented at ICCV 2023", +... ) +\>>> collection.slug +"username/iccv-2023-64f9a55bb3115b4f513ec026" + +#### create\_commit + +[](#huggingface_hub.HfApi.create_commit)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L4014) + +( repo\_id: stroperations: Iterable\[CommitOperation\]commit\_message: strcommit\_description: Optional\[str\] = Nonetoken: Union\[str, bool, None\] = Nonerepo\_type: Optional\[str\] = Nonerevision: Optional\[str\] = Nonecreate\_pr: Optional\[bool\] = Nonenum\_threads: int = 5parent\_commit: Optional\[str\] = Nonerun\_as\_future: bool = False ) → export const metadata = 'undefined';[CommitInfo](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.CommitInfo) or `Future` + +Expand 11 parameters + +Parameters + +* [](#huggingface_hub.HfApi.create_commit.repo_id)**repo\_id** (`str`) — The repository in which the commit will be created, for example: `"username/custom_transformers"` +* [](#huggingface_hub.HfApi.create_commit.operations)**operations** (`Iterable` of `CommitOperation()`) — An iterable of operations to include in the commit, either: + + * [CommitOperationAdd](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.CommitOperationAdd) to upload a file + * [CommitOperationDelete](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.CommitOperationDelete) to delete a file + * [CommitOperationCopy](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.CommitOperationCopy) to copy a file + + Operation objects will be mutated to include information relative to the upload. Do not reuse the same objects for multiple commits. + +* [](#huggingface_hub.HfApi.create_commit.commit_message)**commit\_message** (`str`) — The summary (first line) of the commit that will be created. +* [](#huggingface_hub.HfApi.create_commit.commit_description)**commit\_description** (`str`, _optional_) — The description of the commit that will be created +* [](#huggingface_hub.HfApi.create_commit.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. +* [](#huggingface_hub.HfApi.create_commit.repo_type)**repo\_type** (`str`, _optional_) — Set to `"dataset"` or `"space"` if uploading to a dataset or space, `None` or `"model"` if uploading to a model. Default is `None`. +* [](#huggingface_hub.HfApi.create_commit.revision)**revision** (`str`, _optional_) — The git revision to commit from. Defaults to the head of the `"main"` branch. +* [](#huggingface_hub.HfApi.create_commit.create_pr)**create\_pr** (`boolean`, _optional_) — Whether or not to create a Pull Request with that commit. Defaults to `False`. If `revision` is not set, PR is opened against the `"main"` branch. If `revision` is set and is a branch, PR is opened against this branch. If `revision` is set and is not a branch name (example: a commit oid), an `RevisionNotFoundError` is returned by the server. +* [](#huggingface_hub.HfApi.create_commit.num_threads)**num\_threads** (`int`, _optional_) — Number of concurrent threads for uploading files. Defaults to 5. Setting it to 2 means at most 2 files will be uploaded concurrently. +* [](#huggingface_hub.HfApi.create_commit.parent_commit)**parent\_commit** (`str`, _optional_) — The OID / SHA of the parent commit, as a hexadecimal string. Shorthands (7 first characters) are also supported. If specified and `create_pr` is `False`, the commit will fail if `revision` does not point to `parent_commit`. If specified and `create_pr` is `True`, the pull request will be created from `parent_commit`. Specifying `parent_commit` ensures the repo has not changed before committing the changes, and can be especially useful if the repo is updated / committed to concurrently. +* [](#huggingface_hub.HfApi.create_commit.run_as_future)**run\_as\_future** (`bool`, _optional_) — Whether or not to run this method in the background. Background jobs are run sequentially without blocking the main thread. Passing `run_as_future=True` will return a [Future](https://docs.python.org/3/library/concurrent.futures.html#future-objects) object. Defaults to `False`. + +Returns + +export const metadata = 'undefined'; + +[CommitInfo](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.CommitInfo) or `Future` + +export const metadata = 'undefined'; + +Instance of [CommitInfo](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.CommitInfo) containing information about the newly created commit (commit hash, commit url, pr url, commit message,…). If `run_as_future=True` is passed, returns a Future object which will contain the result when executed. + +Raises + +export const metadata = 'undefined'; + +`ValueError` or [RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) + +export const metadata = 'undefined'; + +* [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError) — If commit message is empty. +* [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError) — If parent commit is not a valid commit OID. +* [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError) — If a README.md file with an invalid metadata section is committed. In this case, the commit will fail early, before trying to upload any file. +* [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError) — If `create_pr` is `True` and revision is neither `None` nor `"main"`. +* [RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) — If repository is not found (error 404): wrong repo\_id/repo\_type, private but not authenticated or repo does not exist. + +Creates a commit in the given repo, deleting & uploading files as needed. + +The input list of `CommitOperation` will be mutated during the commit process. Do not reuse the same objects for multiple commits. + +`create_commit` assumes that the repo already exists on the Hub. If you get a Client error 404, please make sure you are authenticated and that `repo_id` and `repo_type` are set correctly. If repo does not exist, create it first using [create\_repo()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.create_repo). + +`create_commit` is limited to 25k LFS files and a 1GB payload for regular files. + +#### create\_discussion + +[](#huggingface_hub.HfApi.create_discussion)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L6346) + +( repo\_id: strtitle: strtoken: Union\[bool, str, None\] = Nonedescription: Optional\[str\] = Nonerepo\_type: Optional\[str\] = Nonepull\_request: bool = False ) + +Parameters + +* [](#huggingface_hub.HfApi.create_discussion.repo_id)**repo\_id** (`str`) — A namespace (user or an organization) and a repo name separated by a `/`. +* [](#huggingface_hub.HfApi.create_discussion.title)**title** (`str`) — The title of the discussion. It can be up to 200 characters long, and must be at least 3 characters long. Leading and trailing whitespaces will be stripped. +* [](#huggingface_hub.HfApi.create_discussion.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. +* [](#huggingface_hub.HfApi.create_discussion.description)**description** (`str`, _optional_) — An optional description for the Pull Request. Defaults to `"Discussion opened with the huggingface_hub Python library"` +* [](#huggingface_hub.HfApi.create_discussion.pull_request)**pull\_request** (`bool`, _optional_) — Whether to create a Pull Request or discussion. If `True`, creates a Pull Request. If `False`, creates a discussion. Defaults to `False`. +* [](#huggingface_hub.HfApi.create_discussion.repo_type)**repo\_type** (`str`, _optional_) — Set to `"dataset"` or `"space"` if uploading to a dataset or space, `None` or `"model"` if uploading to a model. Default is `None`. + +Creates a Discussion or Pull Request. + +Pull Requests created programmatically will be in `"draft"` status. + +Creating a Pull Request with changes can also be done at once with [HfApi.create\_commit()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.create_commit). + +Returns: [DiscussionWithDetails](/docs/huggingface_hub/v0.30.2/en/package_reference/community#huggingface_hub.DiscussionWithDetails) + +Raises the following errors: + +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) if the HuggingFace API returned an error +* [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError) if some parameter value is invalid +* [RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) If the repository to download from cannot be found. This may be because it doesn’t exist, or because it is set to `private` and you do not have access. + +#### create\_inference\_endpoint + +[](#huggingface_hub.HfApi.create_inference_endpoint)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L7549) + +( name: strrepository: strframework: straccelerator: strinstance\_size: strinstance\_type: strregion: strvendor: straccount\_id: Optional\[str\] = Nonemin\_replica: int = 0max\_replica: int = 1scale\_to\_zero\_timeout: int = 15revision: Optional\[str\] = Nonetask: Optional\[str\] = Nonecustom\_image: Optional\[Dict\] = Nonesecrets: Optional\[Dict\[str, str\]\] = Nonetype: InferenceEndpointType = namespace: Optional\[str\] = Nonetoken: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';[InferenceEndpoint](/docs/huggingface_hub/v0.30.2/en/package_reference/inference_endpoints#huggingface_hub.InferenceEndpoint) + +Expand 19 parameters + +Parameters + +* [](#huggingface_hub.HfApi.create_inference_endpoint.name)**name** (`str`) — The unique name for the new Inference Endpoint. +* [](#huggingface_hub.HfApi.create_inference_endpoint.repository)**repository** (`str`) — The name of the model repository associated with the Inference Endpoint (e.g. `"gpt2"`). +* [](#huggingface_hub.HfApi.create_inference_endpoint.framework)**framework** (`str`) — The machine learning framework used for the model (e.g. `"custom"`). +* [](#huggingface_hub.HfApi.create_inference_endpoint.accelerator)**accelerator** (`str`) — The hardware accelerator to be used for inference (e.g. `"cpu"`). +* [](#huggingface_hub.HfApi.create_inference_endpoint.instance_size)**instance\_size** (`str`) — The size or type of the instance to be used for hosting the model (e.g. `"x4"`). +* [](#huggingface_hub.HfApi.create_inference_endpoint.instance_type)**instance\_type** (`str`) — The cloud instance type where the Inference Endpoint will be deployed (e.g. `"intel-icl"`). +* [](#huggingface_hub.HfApi.create_inference_endpoint.region)**region** (`str`) — The cloud region in which the Inference Endpoint will be created (e.g. `"us-east-1"`). +* [](#huggingface_hub.HfApi.create_inference_endpoint.vendor)**vendor** (`str`) — The cloud provider or vendor where the Inference Endpoint will be hosted (e.g. `"aws"`). +* [](#huggingface_hub.HfApi.create_inference_endpoint.account_id)**account\_id** (`str`, _optional_) — The account ID used to link a VPC to a private Inference Endpoint (if applicable). +* [](#huggingface_hub.HfApi.create_inference_endpoint.min_replica)**min\_replica** (`int`, _optional_) — The minimum number of replicas (instances) to keep running for the Inference Endpoint. Defaults to 0. +* [](#huggingface_hub.HfApi.create_inference_endpoint.max_replica)**max\_replica** (`int`, _optional_) — The maximum number of replicas (instances) to scale to for the Inference Endpoint. Defaults to 1. +* [](#huggingface_hub.HfApi.create_inference_endpoint.scale_to_zero_timeout)**scale\_to\_zero\_timeout** (`int`, _optional_) — The duration in minutes before an inactive endpoint is scaled to zero. Defaults to 15. +* [](#huggingface_hub.HfApi.create_inference_endpoint.revision)**revision** (`str`, _optional_) — The specific model revision to deploy on the Inference Endpoint (e.g. `"6c0e6080953db56375760c0471a8c5f2929baf11"`). +* [](#huggingface_hub.HfApi.create_inference_endpoint.task)**task** (`str`, _optional_) — The task on which to deploy the model (e.g. `"text-classification"`). +* [](#huggingface_hub.HfApi.create_inference_endpoint.custom_image)**custom\_image** (`Dict`, _optional_) — A custom Docker image to use for the Inference Endpoint. This is useful if you want to deploy an Inference Endpoint running on the `text-generation-inference` (TGI) framework (see examples). +* [](#huggingface_hub.HfApi.create_inference_endpoint.secrets)**secrets** (`Dict[str, str]`, _optional_) — Secret values to inject in the container environment. +* [](#huggingface_hub.HfApi.create_inference_endpoint.type)**type** (\[\`InferenceEndpointType\]`, *optional*) -- The type of the Inference Endpoint, which can be` “protected”`(default),`“public”`or`“private”\`. +* [](#huggingface_hub.HfApi.create_inference_endpoint.namespace)**namespace** (`str`, _optional_) — The namespace where the Inference Endpoint will be created. Defaults to the current user’s namespace. +* [](#huggingface_hub.HfApi.create_inference_endpoint.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +[InferenceEndpoint](/docs/huggingface_hub/v0.30.2/en/package_reference/inference_endpoints#huggingface_hub.InferenceEndpoint) + +export const metadata = 'undefined'; + +information about the updated Inference Endpoint. + +Create a new Inference Endpoint. + +[](#huggingface_hub.HfApi.create_inference_endpoint.example) + +Example: + +Copied + +\>>> from huggingface\_hub import HfApi +\>>> api = HfApi() +\>>> endpoint = api.create\_inference\_endpoint( +... "my-endpoint-name", +... repository="gpt2", +... framework="pytorch", +... task="text-generation", +... accelerator="cpu", +... vendor="aws", +... region="us-east-1", +... type\="protected", +... instance\_size="x2", +... instance\_type="intel-icl", +... ) +\>>> endpoint +InferenceEndpoint(name='my-endpoint-name', status="pending",...) + +\# Run inference on the endpoint +\>>> endpoint.client.text\_generation(...) +"..." + +[](#huggingface_hub.HfApi.create_inference_endpoint.example-2) + +Copied + +\# Start an Inference Endpoint running Zephyr-7b-beta on TGI +\>>> from huggingface\_hub import HfApi +\>>> api = HfApi() +\>>> endpoint = api.create\_inference\_endpoint( +... "aws-zephyr-7b-beta-0486", +... repository="HuggingFaceH4/zephyr-7b-beta", +... framework="pytorch", +... task="text-generation", +... accelerator="gpu", +... vendor="aws", +... region="us-east-1", +... type\="protected", +... instance\_size="x1", +... instance\_type="nvidia-a10g", +... custom\_image={ +... "health\_route": "/health", +... "env": { +... "MAX\_BATCH\_PREFILL\_TOKENS": "2048", +... "MAX\_INPUT\_LENGTH": "1024", +... "MAX\_TOTAL\_TOKENS": "1512", +... "MODEL\_ID": "/repository" +... }, +... "url": "ghcr.io/huggingface/text-generation-inference:1.1.0", +... }, +... secrets={"MY\_SECRET\_KEY": "secret\_value"}, +... ) + +#### create\_inference\_endpoint\_from\_catalog + +[](#huggingface_hub.HfApi.create_inference_endpoint_from_catalog)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L7715) + +( repo\_id: strname: Optional\[str\] = Nonetoken: Union\[bool, str, None\] = Nonenamespace: Optional\[str\] = None ) → export const metadata = 'undefined';[InferenceEndpoint](/docs/huggingface_hub/v0.30.2/en/package_reference/inference_endpoints#huggingface_hub.InferenceEndpoint) + +Parameters + +* [](#huggingface_hub.HfApi.create_inference_endpoint_from_catalog.repo_id)**repo\_id** (`str`) — The ID of the model in the catalog to deploy as an Inference Endpoint. +* [](#huggingface_hub.HfApi.create_inference_endpoint_from_catalog.name)**name** (`str`, _optional_) — The unique name for the new Inference Endpoint. If not provided, a random name will be generated. +* [](#huggingface_hub.HfApi.create_inference_endpoint_from_catalog.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). +* [](#huggingface_hub.HfApi.create_inference_endpoint_from_catalog.namespace)**namespace** (`str`, _optional_) — The namespace where the Inference Endpoint will be created. Defaults to the current user’s namespace. + +Returns + +export const metadata = 'undefined'; + +[InferenceEndpoint](/docs/huggingface_hub/v0.30.2/en/package_reference/inference_endpoints#huggingface_hub.InferenceEndpoint) + +export const metadata = 'undefined'; + +information about the new Inference Endpoint. + +Create a new Inference Endpoint from a model in the Hugging Face Inference Catalog. + +The goal of the Inference Catalog is to provide a curated list of models that are optimized for inference and for which default configurations have been tested. See [https://endpoints.huggingface.co/catalog](https://endpoints.huggingface.co/catalog) for a list of available models in the catalog. + +`create_inference_endpoint_from_catalog` is experimental. Its API is subject to change in the future. Please provide feedback if you have any suggestions or requests. + +#### create\_pull\_request + +[](#huggingface_hub.HfApi.create_pull_request)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L6438) + +( repo\_id: strtitle: strtoken: Union\[bool, str, None\] = Nonedescription: Optional\[str\] = Nonerepo\_type: Optional\[str\] = None ) + +Parameters + +* [](#huggingface_hub.HfApi.create_pull_request.repo_id)**repo\_id** (`str`) — A namespace (user or an organization) and a repo name separated by a `/`. +* [](#huggingface_hub.HfApi.create_pull_request.title)**title** (`str`) — The title of the discussion. It can be up to 200 characters long, and must be at least 3 characters long. Leading and trailing whitespaces will be stripped. +* [](#huggingface_hub.HfApi.create_pull_request.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. +* [](#huggingface_hub.HfApi.create_pull_request.description)**description** (`str`, _optional_) — An optional description for the Pull Request. Defaults to `"Discussion opened with the huggingface_hub Python library"` +* [](#huggingface_hub.HfApi.create_pull_request.repo_type)**repo\_type** (`str`, _optional_) — Set to `"dataset"` or `"space"` if uploading to a dataset or space, `None` or `"model"` if uploading to a model. Default is `None`. + +Creates a Pull Request . Pull Requests created programmatically will be in `"draft"` status. + +Creating a Pull Request with changes can also be done at once with [HfApi.create\_commit()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.create_commit); + +This is a wrapper around [HfApi.create\_discussion()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.create_discussion). + +Returns: [DiscussionWithDetails](/docs/huggingface_hub/v0.30.2/en/package_reference/community#huggingface_hub.DiscussionWithDetails) + +Raises the following errors: + +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) if the HuggingFace API returned an error +* [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError) if some parameter value is invalid +* [RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) If the repository to download from cannot be found. This may be because it doesn’t exist, or because it is set to `private` and you do not have access. + +#### create\_repo + +[](#huggingface_hub.HfApi.create_repo)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L3580) + +( repo\_id: strtoken: Union\[str, bool, None\] = Noneprivate: Optional\[bool\] = Nonerepo\_type: Optional\[str\] = Noneexist\_ok: bool = Falseresource\_group\_id: Optional\[str\] = Nonespace\_sdk: Optional\[str\] = Nonespace\_hardware: Optional\[SpaceHardware\] = Nonespace\_storage: Optional\[SpaceStorage\] = Nonespace\_sleep\_time: Optional\[int\] = Nonespace\_secrets: Optional\[List\[Dict\[str, str\]\]\] = Nonespace\_variables: Optional\[List\[Dict\[str, str\]\]\] = None ) → export const metadata = 'undefined';[RepoUrl](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.RepoUrl) + +Expand 12 parameters + +Parameters + +* [](#huggingface_hub.HfApi.create_repo.repo_id)**repo\_id** (`str`) — A namespace (user or an organization) and a repo name separated by a `/`. +* [](#huggingface_hub.HfApi.create_repo.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. +* [](#huggingface_hub.HfApi.create_repo.private)**private** (`bool`, _optional_) — Whether to make the repo private. If `None` (default), the repo will be public unless the organization’s default is private. This value is ignored if the repo already exists. +* [](#huggingface_hub.HfApi.create_repo.repo_type)**repo\_type** (`str`, _optional_) — Set to `"dataset"` or `"space"` if uploading to a dataset or space, `None` or `"model"` if uploading to a model. Default is `None`. +* [](#huggingface_hub.HfApi.create_repo.exist_ok)**exist\_ok** (`bool`, _optional_, defaults to `False`) — If `True`, do not raise an error if repo already exists. +* [](#huggingface_hub.HfApi.create_repo.resource_group_id)**resource\_group\_id** (`str`, _optional_) — Resource group in which to create the repo. Resource groups is only available for organizations and allow to define which members of the organization can access the resource. The ID of a resource group can be found in the URL of the resource’s page on the Hub (e.g. `"66670e5163145ca562cb1988"`). To learn more about resource groups, see [https://huggingface.co/docs/hub/en/security-resource-groups](https://huggingface.co/docs/hub/en/security-resource-groups). +* [](#huggingface_hub.HfApi.create_repo.space_sdk)**space\_sdk** (`str`, _optional_) — Choice of SDK to use if repo\_type is “space”. Can be “streamlit”, “gradio”, “docker”, or “static”. +* [](#huggingface_hub.HfApi.create_repo.space_hardware)**space\_hardware** (`SpaceHardware` or `str`, _optional_) — Choice of Hardware if repo\_type is “space”. See [SpaceHardware](/docs/huggingface_hub/v0.30.2/en/package_reference/space_runtime#huggingface_hub.SpaceHardware) for a complete list. +* [](#huggingface_hub.HfApi.create_repo.space_storage)**space\_storage** (`SpaceStorage` or `str`, _optional_) — Choice of persistent storage tier. Example: `"small"`. See [SpaceStorage](/docs/huggingface_hub/v0.30.2/en/package_reference/space_runtime#huggingface_hub.SpaceStorage) for a complete list. +* [](#huggingface_hub.HfApi.create_repo.space_sleep_time)**space\_sleep\_time** (`int`, _optional_) — Number of seconds of inactivity to wait before a Space is put to sleep. Set to `-1` if you don’t want your Space to sleep (default behavior for upgraded hardware). For free hardware, you can’t configure the sleep time (value is fixed to 48 hours of inactivity). See [https://huggingface.co/docs/hub/spaces-gpus#sleep-time](https://huggingface.co/docs/hub/spaces-gpus#sleep-time) for more details. +* [](#huggingface_hub.HfApi.create_repo.space_secrets)**space\_secrets** (`List[Dict[str, str]]`, _optional_) — A list of secret keys to set in your Space. Each item is in the form `{"key": ..., "value": ..., "description": ...}` where description is optional. For more details, see [https://huggingface.co/docs/hub/spaces-overview#managing-secrets](https://huggingface.co/docs/hub/spaces-overview#managing-secrets). +* [](#huggingface_hub.HfApi.create_repo.space_variables)**space\_variables** (`List[Dict[str, str]]`, _optional_) — A list of public environment variables to set in your Space. Each item is in the form `{"key": ..., "value": ..., "description": ...}` where description is optional. For more details, see [https://huggingface.co/docs/hub/spaces-overview#managing-secrets-and-environment-variables](https://huggingface.co/docs/hub/spaces-overview#managing-secrets-and-environment-variables). + +Returns + +export const metadata = 'undefined'; + +[RepoUrl](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.RepoUrl) + +export const metadata = 'undefined'; + +URL to the newly created repo. Value is a subclass of `str` containing attributes like `endpoint`, `repo_type` and `repo_id`. + +Create an empty repo on the HuggingFace Hub. + +#### create\_tag + +[](#huggingface_hub.HfApi.create_tag)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L5998) + +( repo\_id: strtag: strtag\_message: Optional\[str\] = Nonerevision: Optional\[str\] = Nonetoken: Union\[bool, str, None\] = Nonerepo\_type: Optional\[str\] = Noneexist\_ok: bool = False ) + +Expand 7 parameters + +Parameters + +* [](#huggingface_hub.HfApi.create_tag.repo_id)**repo\_id** (`str`) — The repository in which a commit will be tagged. Example: `"user/my-cool-model"`. +* [](#huggingface_hub.HfApi.create_tag.tag)**tag** (`str`) — The name of the tag to create. +* [](#huggingface_hub.HfApi.create_tag.tag_message)**tag\_message** (`str`, _optional_) — The description of the tag to create. +* [](#huggingface_hub.HfApi.create_tag.revision)**revision** (`str`, _optional_) — The git revision to tag. It can be a branch name or the OID/SHA of a commit, as a hexadecimal string. Shorthands (7 first characters) are also supported. Defaults to the head of the `"main"` branch. +* [](#huggingface_hub.HfApi.create_tag.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. +* [](#huggingface_hub.HfApi.create_tag.repo_type)**repo\_type** (`str`, _optional_) — Set to `"dataset"` or `"space"` if tagging a dataset or space, `None` or `"model"` if tagging a model. Default is `None`. +* [](#huggingface_hub.HfApi.create_tag.exist_ok)**exist\_ok** (`bool`, _optional_, defaults to `False`) — If `True`, do not raise an error if tag already exists. + +Raises + +export const metadata = 'undefined'; + +[RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) or [RevisionNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RevisionNotFoundError) or [HfHubHTTPError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.HfHubHTTPError) + +export const metadata = 'undefined'; + +* [RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) — If repository is not found (error 404): wrong repo\_id/repo\_type, private but not authenticated or repo does not exist. +* [RevisionNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RevisionNotFoundError) — If revision is not found (error 404) on the repo. +* [HfHubHTTPError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.HfHubHTTPError) — If the branch already exists on the repo (error 409) and `exist_ok` is set to `False`. + +Tag a given commit of a repo on the Hub. + +#### create\_webhook + +[](#huggingface_hub.HfApi.create_webhook)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L9118) + +( url: strwatched: List\[Union\[Dict, WebhookWatchedItem\]\]domains: Optional\[List\[constants.WEBHOOK\_DOMAIN\_T\]\] = Nonesecret: Optional\[str\] = Nonetoken: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';[WebhookInfo](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.WebhookInfo) + +Parameters + +* [](#huggingface_hub.HfApi.create_webhook.url)**url** (`str`) — URL to send the payload to. +* [](#huggingface_hub.HfApi.create_webhook.watched)**watched** (`List[WebhookWatchedItem]`) — List of [WebhookWatchedItem](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.WebhookWatchedItem) to be watched by the webhook. It can be users, orgs, models, datasets or spaces. Watched items can also be provided as plain dictionaries. +* [](#huggingface_hub.HfApi.create_webhook.domains)**domains** (`List[Literal["repo", "discussion"]]`, optional) — List of domains to watch. It can be “repo”, “discussion” or both. +* [](#huggingface_hub.HfApi.create_webhook.secret)**secret** (`str`, optional) — A secret to sign the payload with. +* [](#huggingface_hub.HfApi.create_webhook.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +[WebhookInfo](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.WebhookInfo) + +export const metadata = 'undefined'; + +Info about the newly created webhook. + +Create a new webhook. + +[](#huggingface_hub.HfApi.create_webhook.example) + +Example: + +Copied + +\>>> from huggingface\_hub import create\_webhook +\>>> payload = create\_webhook( +... watched=\[{"type": "user", "name": "julien-c"}, {"type": "org", "name": "HuggingFaceH4"}\], +... url="https://webhook.site/a2176e82-5720-43ee-9e06-f91cb4c91548", +... domains=\["repo", "discussion"\], +... secret="my-secret", +... ) +\>>> print(payload) +WebhookInfo( + id\="654bbbc16f2ec14d77f109cc", + url="https://webhook.site/a2176e82-5720-43ee-9e06-f91cb4c91548", + watched=\[WebhookWatchedItem(type\="user", name="julien-c"), WebhookWatchedItem(type\="org", name="HuggingFaceH4")\], + domains=\["repo", "discussion"\], + secret="my-secret", + disabled=False, +) + +#### dataset\_info + +[](#huggingface_hub.HfApi.dataset_info)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L2596) + +( repo\_id: strrevision: Optional\[str\] = Nonetimeout: Optional\[float\] = Nonefiles\_metadata: bool = Falseexpand: Optional\[List\[ExpandDatasetProperty\_T\]\] = Nonetoken: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';[hf\_api.DatasetInfo](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.DatasetInfo) + +Expand 6 parameters + +Parameters + +* [](#huggingface_hub.HfApi.dataset_info.repo_id)**repo\_id** (`str`) — A namespace (user or an organization) and a repo name separated by a `/`. +* [](#huggingface_hub.HfApi.dataset_info.revision)**revision** (`str`, _optional_) — The revision of the dataset repository from which to get the information. +* [](#huggingface_hub.HfApi.dataset_info.timeout)**timeout** (`float`, _optional_) — Whether to set a timeout for the request to the Hub. +* [](#huggingface_hub.HfApi.dataset_info.files_metadata)**files\_metadata** (`bool`, _optional_) — Whether or not to retrieve metadata for files in the repository (size, LFS metadata, etc). Defaults to `False`. +* [](#huggingface_hub.HfApi.dataset_info.expand)**expand** (`List[ExpandDatasetProperty_T]`, _optional_) — List properties to return in the response. When used, only the properties in the list will be returned. This parameter cannot be used if `files_metadata` is passed. Possible values are `"author"`, `"cardData"`, `"citation"`, `"createdAt"`, `"disabled"`, `"description"`, `"downloads"`, `"downloadsAllTime"`, `"gated"`, `"lastModified"`, `"likes"`, `"paperswithcode_id"`, `"private"`, `"siblings"`, `"sha"`, `"tags"`, `"trendingScore"`,`"usedStorage"`, `"resourceGroup"` and `"xetEnabled"`. +* [](#huggingface_hub.HfApi.dataset_info.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +[hf\_api.DatasetInfo](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.DatasetInfo) + +export const metadata = 'undefined'; + +The dataset repository information. + +Get info on one specific dataset on huggingface.co. + +Dataset can be private if you pass an acceptable token. + +Raises the following errors: + +* [RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) If the repository to download from cannot be found. This may be because it doesn’t exist, or because it is set to `private` and you do not have access. +* [RevisionNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RevisionNotFoundError) If the revision to download from cannot be found. + +#### delete\_branch + +[](#huggingface_hub.HfApi.delete_branch)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L5946) + +( repo\_id: strbranch: strtoken: Union\[bool, str, None\] = Nonerepo\_type: Optional\[str\] = None ) + +Parameters + +* [](#huggingface_hub.HfApi.delete_branch.repo_id)**repo\_id** (`str`) — The repository in which a branch will be deleted. Example: `"user/my-cool-model"`. +* [](#huggingface_hub.HfApi.delete_branch.branch)**branch** (`str`) — The name of the branch to delete. +* [](#huggingface_hub.HfApi.delete_branch.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. +* [](#huggingface_hub.HfApi.delete_branch.repo_type)**repo\_type** (`str`, _optional_) — Set to `"dataset"` or `"space"` if creating a branch on a dataset or space, `None` or `"model"` if tagging a model. Default is `None`. + +Raises + +export const metadata = 'undefined'; + +[RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) or [HfHubHTTPError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.HfHubHTTPError) + +export const metadata = 'undefined'; + +* [RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) — If repository is not found (error 404): wrong repo\_id/repo\_type, private but not authenticated or repo does not exist. +* [HfHubHTTPError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.HfHubHTTPError) — If trying to delete a protected branch. Ex: `main` cannot be deleted. +* [HfHubHTTPError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.HfHubHTTPError) — If trying to delete a branch that does not exist. + +Delete a branch from a repo on the Hub. + +#### delete\_collection + +[](#huggingface_hub.HfApi.delete_collection)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L8350) + +( collection\_slug: strmissing\_ok: bool = Falsetoken: Union\[bool, str, None\] = None ) + +Parameters + +* [](#huggingface_hub.HfApi.delete_collection.collection_slug)**collection\_slug** (`str`) — Slug of the collection to delete. Example: `"TheBloke/recent-models-64f9a55bb3115b4f513ec026"`. +* [](#huggingface_hub.HfApi.delete_collection.missing_ok)**missing\_ok** (`bool`, _optional_) — If `True`, do not raise an error if collection doesn’t exists. +* [](#huggingface_hub.HfApi.delete_collection.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Delete a collection on the Hub. + +[](#huggingface_hub.HfApi.delete_collection.example) + +Example: + +Copied + +\>>> from huggingface\_hub import delete\_collection +\>>> collection = delete\_collection("username/useless-collection-64f9a55bb3115b4f513ec026", missing\_ok=True) + +This is a non-revertible action. A deleted collection cannot be restored. + +#### delete\_collection\_item + +[](#huggingface_hub.HfApi.delete_collection_item)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L8526) + +( collection\_slug: stritem\_object\_id: strmissing\_ok: bool = Falsetoken: Union\[bool, str, None\] = None ) + +Parameters + +* [](#huggingface_hub.HfApi.delete_collection_item.collection_slug)**collection\_slug** (`str`) — Slug of the collection to update. Example: `"TheBloke/recent-models-64f9a55bb3115b4f513ec026"`. +* [](#huggingface_hub.HfApi.delete_collection_item.item_object_id)**item\_object\_id** (`str`) — ID of the item in the collection. This is not the id of the item on the Hub (repo\_id or paper id). It must be retrieved from a [CollectionItem](/docs/huggingface_hub/v0.30.2/en/package_reference/collections#huggingface_hub.CollectionItem) object. Example: `collection.items[0].item_object_id`. +* [](#huggingface_hub.HfApi.delete_collection_item.missing_ok)**missing\_ok** (`bool`, _optional_) — If `True`, do not raise an error if item doesn’t exists. +* [](#huggingface_hub.HfApi.delete_collection_item.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Delete an item from a collection. + +[](#huggingface_hub.HfApi.delete_collection_item.example) + +Example: + +Copied + +\>>> from huggingface\_hub import get\_collection, delete\_collection\_item + +\# Get collection first +\>>> collection = get\_collection("TheBloke/recent-models-64f9a55bb3115b4f513ec026") + +\# Delete item based on its ID +\>>> delete\_collection\_item( +... collection\_slug="TheBloke/recent-models-64f9a55bb3115b4f513ec026", +... item\_object\_id=collection.items\[-1\].item\_object\_id, +... ) + +#### delete\_file + +[](#huggingface_hub.HfApi.delete_file)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L4964) + +( path\_in\_repo: strrepo\_id: strtoken: Union\[str, bool, None\] = Nonerepo\_type: Optional\[str\] = Nonerevision: Optional\[str\] = Nonecommit\_message: Optional\[str\] = Nonecommit\_description: Optional\[str\] = Nonecreate\_pr: Optional\[bool\] = Noneparent\_commit: Optional\[str\] = None ) + +Expand 9 parameters + +Parameters + +* [](#huggingface_hub.HfApi.delete_file.path_in_repo)**path\_in\_repo** (`str`) — Relative filepath in the repo, for example: `"checkpoints/1fec34a/weights.bin"` +* [](#huggingface_hub.HfApi.delete_file.repo_id)**repo\_id** (`str`) — The repository from which the file will be deleted, for example: `"username/custom_transformers"` +* [](#huggingface_hub.HfApi.delete_file.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. +* [](#huggingface_hub.HfApi.delete_file.repo_type)**repo\_type** (`str`, _optional_) — Set to `"dataset"` or `"space"` if the file is in a dataset or space, `None` or `"model"` if in a model. Default is `None`. +* [](#huggingface_hub.HfApi.delete_file.revision)**revision** (`str`, _optional_) — The git revision to commit from. Defaults to the head of the `"main"` branch. +* [](#huggingface_hub.HfApi.delete_file.commit_message)**commit\_message** (`str`, _optional_) — The summary / title / first line of the generated commit. Defaults to `f"Delete {path_in_repo} with huggingface_hub"`. +* [](#huggingface_hub.HfApi.delete_file.commit_description)**commit\_description** (`str` _optional_) — The description of the generated commit +* [](#huggingface_hub.HfApi.delete_file.create_pr)**create\_pr** (`boolean`, _optional_) — Whether or not to create a Pull Request with that commit. Defaults to `False`. If `revision` is not set, PR is opened against the `"main"` branch. If `revision` is set and is a branch, PR is opened against this branch. If `revision` is set and is not a branch name (example: a commit oid), an `RevisionNotFoundError` is returned by the server. +* [](#huggingface_hub.HfApi.delete_file.parent_commit)**parent\_commit** (`str`, _optional_) — The OID / SHA of the parent commit, as a hexadecimal string. Shorthands (7 first characters) are also supported. If specified and `create_pr` is `False`, the commit will fail if `revision` does not point to `parent_commit`. If specified and `create_pr` is `True`, the pull request will be created from `parent_commit`. Specifying `parent_commit` ensures the repo has not changed before committing the changes, and can be especially useful if the repo is updated / committed to concurrently. + +Deletes a file in the given repo. + +Raises the following errors: + +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) if the HuggingFace API returned an error +* [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError) if some parameter value is invalid +* [RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) If the repository to download from cannot be found. This may be because it doesn’t exist, or because it is set to `private` and you do not have access. +* [RevisionNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RevisionNotFoundError) If the revision to download from cannot be found. +* [EntryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.EntryNotFoundError) If the file to download cannot be found. + +#### delete\_files + +[](#huggingface_hub.HfApi.delete_files)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L5054) + +( repo\_id: strdelete\_patterns: List\[str\]token: Union\[bool, str, None\] = Nonerepo\_type: Optional\[str\] = Nonerevision: Optional\[str\] = Nonecommit\_message: Optional\[str\] = Nonecommit\_description: Optional\[str\] = Nonecreate\_pr: Optional\[bool\] = Noneparent\_commit: Optional\[str\] = None ) + +Expand 9 parameters + +Parameters + +* [](#huggingface_hub.HfApi.delete_files.repo_id)**repo\_id** (`str`) — The repository from which the folder will be deleted, for example: `"username/custom_transformers"` +* [](#huggingface_hub.HfApi.delete_files.delete_patterns)**delete\_patterns** (`List[str]`) — List of files or folders to delete. Each string can either be a file path, a folder path or a Unix shell-style wildcard. E.g. `["file.txt", "folder/", "data/*.parquet"]` +* [](#huggingface_hub.HfApi.delete_files.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. to the stored token. +* [](#huggingface_hub.HfApi.delete_files.repo_type)**repo\_type** (`str`, _optional_) — Type of the repo to delete files from. Can be `"model"`, `"dataset"` or `"space"`. Defaults to `"model"`. +* [](#huggingface_hub.HfApi.delete_files.revision)**revision** (`str`, _optional_) — The git revision to commit from. Defaults to the head of the `"main"` branch. +* [](#huggingface_hub.HfApi.delete_files.commit_message)**commit\_message** (`str`, _optional_) — The summary (first line) of the generated commit. Defaults to `f"Delete files using huggingface_hub"`. +* [](#huggingface_hub.HfApi.delete_files.commit_description)**commit\_description** (`str` _optional_) — The description of the generated commit. +* [](#huggingface_hub.HfApi.delete_files.create_pr)**create\_pr** (`boolean`, _optional_) — Whether or not to create a Pull Request with that commit. Defaults to `False`. If `revision` is not set, PR is opened against the `"main"` branch. If `revision` is set and is a branch, PR is opened against this branch. If `revision` is set and is not a branch name (example: a commit oid), an `RevisionNotFoundError` is returned by the server. +* [](#huggingface_hub.HfApi.delete_files.parent_commit)**parent\_commit** (`str`, _optional_) — The OID / SHA of the parent commit, as a hexadecimal string. Shorthands (7 first characters) are also supported. If specified and `create_pr` is `False`, the commit will fail if `revision` does not point to `parent_commit`. If specified and `create_pr` is `True`, the pull request will be created from `parent_commit`. Specifying `parent_commit` ensures the repo has not changed before committing the changes, and can be especially useful if the repo is updated / committed to concurrently. + +Delete files from a repository on the Hub. + +If a folder path is provided, the entire folder is deleted as well as all files it contained. + +#### delete\_folder + +[](#huggingface_hub.HfApi.delete_folder)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L5130) + +( path\_in\_repo: strrepo\_id: strtoken: Union\[bool, str, None\] = Nonerepo\_type: Optional\[str\] = Nonerevision: Optional\[str\] = Nonecommit\_message: Optional\[str\] = Nonecommit\_description: Optional\[str\] = Nonecreate\_pr: Optional\[bool\] = Noneparent\_commit: Optional\[str\] = None ) + +Expand 9 parameters + +Parameters + +* [](#huggingface_hub.HfApi.delete_folder.path_in_repo)**path\_in\_repo** (`str`) — Relative folder path in the repo, for example: `"checkpoints/1fec34a"`. +* [](#huggingface_hub.HfApi.delete_folder.repo_id)**repo\_id** (`str`) — The repository from which the folder will be deleted, for example: `"username/custom_transformers"` +* [](#huggingface_hub.HfApi.delete_folder.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. to the stored token. +* [](#huggingface_hub.HfApi.delete_folder.repo_type)**repo\_type** (`str`, _optional_) — Set to `"dataset"` or `"space"` if the folder is in a dataset or space, `None` or `"model"` if in a model. Default is `None`. +* [](#huggingface_hub.HfApi.delete_folder.revision)**revision** (`str`, _optional_) — The git revision to commit from. Defaults to the head of the `"main"` branch. +* [](#huggingface_hub.HfApi.delete_folder.commit_message)**commit\_message** (`str`, _optional_) — The summary / title / first line of the generated commit. Defaults to `f"Delete folder {path_in_repo} with huggingface_hub"`. +* [](#huggingface_hub.HfApi.delete_folder.commit_description)**commit\_description** (`str` _optional_) — The description of the generated commit. +* [](#huggingface_hub.HfApi.delete_folder.create_pr)**create\_pr** (`boolean`, _optional_) — Whether or not to create a Pull Request with that commit. Defaults to `False`. If `revision` is not set, PR is opened against the `"main"` branch. If `revision` is set and is a branch, PR is opened against this branch. If `revision` is set and is not a branch name (example: a commit oid), an `RevisionNotFoundError` is returned by the server. +* [](#huggingface_hub.HfApi.delete_folder.parent_commit)**parent\_commit** (`str`, _optional_) — The OID / SHA of the parent commit, as a hexadecimal string. Shorthands (7 first characters) are also supported. If specified and `create_pr` is `False`, the commit will fail if `revision` does not point to `parent_commit`. If specified and `create_pr` is `True`, the pull request will be created from `parent_commit`. Specifying `parent_commit` ensures the repo has not changed before committing the changes, and can be especially useful if the repo is updated / committed to concurrently. + +Deletes a folder in the given repo. + +Simple wrapper around [create\_commit()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.create_commit) method. + +#### delete\_inference\_endpoint + +[](#huggingface_hub.HfApi.delete_inference_endpoint)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L7958) + +( name: strnamespace: Optional\[str\] = Nonetoken: Union\[bool, str, None\] = None ) + +Parameters + +* [](#huggingface_hub.HfApi.delete_inference_endpoint.name)**name** (`str`) — The name of the Inference Endpoint to delete. +* [](#huggingface_hub.HfApi.delete_inference_endpoint.namespace)**namespace** (`str`, _optional_) — The namespace in which the Inference Endpoint is located. Defaults to the current user. +* [](#huggingface_hub.HfApi.delete_inference_endpoint.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Delete an Inference Endpoint. + +This operation is not reversible. If you don’t want to be charged for an Inference Endpoint, it is preferable to pause it with [pause\_inference\_endpoint()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.pause_inference_endpoint) or scale it to zero with [scale\_to\_zero\_inference\_endpoint()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.scale_to_zero_inference_endpoint). + +For convenience, you can also delete an Inference Endpoint using [InferenceEndpoint.delete()](/docs/huggingface_hub/v0.30.2/en/package_reference/inference_endpoints#huggingface_hub.InferenceEndpoint.delete). + +#### delete\_repo + +[](#huggingface_hub.HfApi.delete_repo)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L3729) + +( repo\_id: strtoken: Union\[str, bool, None\] = Nonerepo\_type: Optional\[str\] = Nonemissing\_ok: bool = False ) + +Parameters + +* [](#huggingface_hub.HfApi.delete_repo.repo_id)**repo\_id** (`str`) — A namespace (user or an organization) and a repo name separated by a `/`. +* [](#huggingface_hub.HfApi.delete_repo.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. +* [](#huggingface_hub.HfApi.delete_repo.repo_type)**repo\_type** (`str`, _optional_) — Set to `"dataset"` or `"space"` if uploading to a dataset or space, `None` or `"model"` if uploading to a model. +* [](#huggingface_hub.HfApi.delete_repo.missing_ok)**missing\_ok** (`bool`, _optional_, defaults to `False`) — If `True`, do not raise an error if repo does not exist. + +Raises + +export const metadata = 'undefined'; + +[RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) + +export const metadata = 'undefined'; + +* [RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) — If the repository to delete from cannot be found and `missing_ok` is set to False (default). + +Delete a repo from the HuggingFace Hub. CAUTION: this is irreversible. + +#### delete\_space\_secret + +[](#huggingface_hub.HfApi.delete_space_secret)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L6972) + +( repo\_id: strkey: strtoken: Union\[bool, str, None\] = None ) + +Parameters + +* [](#huggingface_hub.HfApi.delete_space_secret.repo_id)**repo\_id** (`str`) — ID of the repo to update. Example: `"bigcode/in-the-stack"`. +* [](#huggingface_hub.HfApi.delete_space_secret.key)**key** (`str`) — Secret key. Example: `"GITHUB_API_KEY"`. +* [](#huggingface_hub.HfApi.delete_space_secret.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Deletes a secret from a Space. + +Secrets allow to set secret keys or tokens to a Space without hardcoding them. For more details, see [https://huggingface.co/docs/hub/spaces-overview#managing-secrets](https://huggingface.co/docs/hub/spaces-overview#managing-secrets). + +#### delete\_space\_storage + +[](#huggingface_hub.HfApi.delete_space_storage)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L7456) + +( repo\_id: strtoken: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';[SpaceRuntime](/docs/huggingface_hub/v0.30.2/en/package_reference/space_runtime#huggingface_hub.SpaceRuntime) + +Parameters + +* [](#huggingface_hub.HfApi.delete_space_storage.repo_id)**repo\_id** (`str`) — ID of the Space to update. Example: `"open-llm-leaderboard/open_llm_leaderboard"`. +* [](#huggingface_hub.HfApi.delete_space_storage.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +[SpaceRuntime](/docs/huggingface_hub/v0.30.2/en/package_reference/space_runtime#huggingface_hub.SpaceRuntime) + +export const metadata = 'undefined'; + +Runtime information about a Space including Space stage and hardware. + +Raises + +export const metadata = 'undefined'; + +`BadRequestError` + +export const metadata = 'undefined'; + +* `BadRequestError` — If space has no persistent storage. + +Delete persistent storage for a Space. + +#### delete\_space\_variable + +[](#huggingface_hub.HfApi.delete_space_variable)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L7061) + +( repo\_id: strkey: strtoken: Union\[bool, str, None\] = None ) + +Parameters + +* [](#huggingface_hub.HfApi.delete_space_variable.repo_id)**repo\_id** (`str`) — ID of the repo to update. Example: `"bigcode/in-the-stack"`. +* [](#huggingface_hub.HfApi.delete_space_variable.key)**key** (`str`) — Variable key. Example: `"MODEL_REPO_ID"` +* [](#huggingface_hub.HfApi.delete_space_variable.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Deletes a variable from a Space. + +Variables allow to set environment variables to a Space without hardcoding them. For more details, see [https://huggingface.co/docs/hub/spaces-overview#managing-secrets-and-environment-variables](https://huggingface.co/docs/hub/spaces-overview#managing-secrets-and-environment-variables) + +#### delete\_tag + +[](#huggingface_hub.HfApi.delete_tag)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L6072) + +( repo\_id: strtag: strtoken: Union\[bool, str, None\] = Nonerepo\_type: Optional\[str\] = None ) + +Parameters + +* [](#huggingface_hub.HfApi.delete_tag.repo_id)**repo\_id** (`str`) — The repository in which a tag will be deleted. Example: `"user/my-cool-model"`. +* [](#huggingface_hub.HfApi.delete_tag.tag)**tag** (`str`) — The name of the tag to delete. +* [](#huggingface_hub.HfApi.delete_tag.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. +* [](#huggingface_hub.HfApi.delete_tag.repo_type)**repo\_type** (`str`, _optional_) — Set to `"dataset"` or `"space"` if tagging a dataset or space, `None` or `"model"` if tagging a model. Default is `None`. + +Raises + +export const metadata = 'undefined'; + +[RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) or [RevisionNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RevisionNotFoundError) + +export const metadata = 'undefined'; + +* [RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) — If repository is not found (error 404): wrong repo\_id/repo\_type, private but not authenticated or repo does not exist. +* [RevisionNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RevisionNotFoundError) — If tag is not found. + +Delete a tag from a repo on the Hub. + +#### delete\_webhook + +[](#huggingface_hub.HfApi.delete_webhook)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L9372) + +( webhook\_id: strtoken: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';`None` + +Parameters + +* [](#huggingface_hub.HfApi.delete_webhook.webhook_id)**webhook\_id** (`str`) — The unique identifier of the webhook to delete. +* [](#huggingface_hub.HfApi.delete_webhook.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +`None` + +Delete a webhook. + +[](#huggingface_hub.HfApi.delete_webhook.example) + +Example: + +Copied + +\>>> from huggingface\_hub import delete\_webhook +\>>> delete\_webhook("654bbbc16f2ec14d77f109cc") + +#### disable\_webhook + +[](#huggingface_hub.HfApi.disable_webhook)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L9321) + +( webhook\_id: strtoken: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';[WebhookInfo](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.WebhookInfo) + +Parameters + +* [](#huggingface_hub.HfApi.disable_webhook.webhook_id)**webhook\_id** (`str`) — The unique identifier of the webhook to disable. +* [](#huggingface_hub.HfApi.disable_webhook.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +[WebhookInfo](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.WebhookInfo) + +export const metadata = 'undefined'; + +Info about the disabled webhook. + +Disable a webhook (makes it “disabled”). + +[](#huggingface_hub.HfApi.disable_webhook.example) + +Example: + +Copied + +\>>> from huggingface\_hub import disable\_webhook +\>>> disabled\_webhook = disable\_webhook("654bbbc16f2ec14d77f109cc") +\>>> disabled\_webhook +WebhookInfo( + id\="654bbbc16f2ec14d77f109cc", + url="https://webhook.site/a2176e82-5720-43ee-9e06-f91cb4c91548", + watched=\[WebhookWatchedItem(type\="user", name="julien-c"), WebhookWatchedItem(type\="org", name="HuggingFaceH4")\], + domains=\["repo", "discussion"\], + secret="my-secret", + disabled=True, +) + +#### duplicate\_space + +[](#huggingface_hub.HfApi.duplicate_space)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L7299) + +( from\_id: strto\_id: Optional\[str\] = Noneprivate: Optional\[bool\] = Nonetoken: Union\[bool, str, None\] = Noneexist\_ok: bool = Falsehardware: Optional\[SpaceHardware\] = Nonestorage: Optional\[SpaceStorage\] = Nonesleep\_time: Optional\[int\] = Nonesecrets: Optional\[List\[Dict\[str, str\]\]\] = Nonevariables: Optional\[List\[Dict\[str, str\]\]\] = None ) → export const metadata = 'undefined';[RepoUrl](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.RepoUrl) + +Expand 10 parameters + +Parameters + +* [](#huggingface_hub.HfApi.duplicate_space.from_id)**from\_id** (`str`) — ID of the Space to duplicate. Example: `"pharma/CLIP-Interrogator"`. +* [](#huggingface_hub.HfApi.duplicate_space.to_id)**to\_id** (`str`, _optional_) — ID of the new Space. Example: `"dog/CLIP-Interrogator"`. If not provided, the new Space will have the same name as the original Space, but in your account. +* [](#huggingface_hub.HfApi.duplicate_space.private)**private** (`bool`, _optional_) — Whether the new Space should be private or not. Defaults to the same privacy as the original Space. +* [](#huggingface_hub.HfApi.duplicate_space.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. +* [](#huggingface_hub.HfApi.duplicate_space.exist_ok)**exist\_ok** (`bool`, _optional_, defaults to `False`) — If `True`, do not raise an error if repo already exists. +* [](#huggingface_hub.HfApi.duplicate_space.hardware)**hardware** (`SpaceHardware` or `str`, _optional_) — Choice of Hardware. Example: `"t4-medium"`. See [SpaceHardware](/docs/huggingface_hub/v0.30.2/en/package_reference/space_runtime#huggingface_hub.SpaceHardware) for a complete list. +* [](#huggingface_hub.HfApi.duplicate_space.storage)**storage** (`SpaceStorage` or `str`, _optional_) — Choice of persistent storage tier. Example: `"small"`. See [SpaceStorage](/docs/huggingface_hub/v0.30.2/en/package_reference/space_runtime#huggingface_hub.SpaceStorage) for a complete list. +* [](#huggingface_hub.HfApi.duplicate_space.sleep_time)**sleep\_time** (`int`, _optional_) — Number of seconds of inactivity to wait before a Space is put to sleep. Set to `-1` if you don’t want your Space to sleep (default behavior for upgraded hardware). For free hardware, you can’t configure the sleep time (value is fixed to 48 hours of inactivity). See [https://huggingface.co/docs/hub/spaces-gpus#sleep-time](https://huggingface.co/docs/hub/spaces-gpus#sleep-time) for more details. +* [](#huggingface_hub.HfApi.duplicate_space.secrets)**secrets** (`List[Dict[str, str]]`, _optional_) — A list of secret keys to set in your Space. Each item is in the form `{"key": ..., "value": ..., "description": ...}` where description is optional. For more details, see [https://huggingface.co/docs/hub/spaces-overview#managing-secrets](https://huggingface.co/docs/hub/spaces-overview#managing-secrets). +* [](#huggingface_hub.HfApi.duplicate_space.variables)**variables** (`List[Dict[str, str]]`, _optional_) — A list of public environment variables to set in your Space. Each item is in the form `{"key": ..., "value": ..., "description": ...}` where description is optional. For more details, see [https://huggingface.co/docs/hub/spaces-overview#managing-secrets-and-environment-variables](https://huggingface.co/docs/hub/spaces-overview#managing-secrets-and-environment-variables). + +Returns + +export const metadata = 'undefined'; + +[RepoUrl](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.RepoUrl) + +export const metadata = 'undefined'; + +URL to the newly created repo. Value is a subclass of `str` containing attributes like `endpoint`, `repo_type` and `repo_id`. + +Raises + +export const metadata = 'undefined'; + +[RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) or `HTTPError` + +export const metadata = 'undefined'; + +* [RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) — If one of `from_id` or `to_id` cannot be found. This may be because it doesn’t exist, or because it is set to `private` and you do not have access. +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) — If the HuggingFace API returned an error + +Duplicate a Space. + +Programmatically duplicate a Space. The new Space will be created in your account and will be in the same state as the original Space (running or paused). You can duplicate a Space no matter the current state of a Space. + +[](#huggingface_hub.HfApi.duplicate_space.example) + +Example: + +Copied + +\>>> from huggingface\_hub import duplicate\_space + +\# Duplicate a Space to your account +\>>> duplicate\_space("multimodalart/dreambooth-training") +RepoUrl('https://huggingface.co/spaces/nateraw/dreambooth-training',...) + +\# Can set custom destination id and visibility flag. +\>>> duplicate\_space("multimodalart/dreambooth-training", to\_id="my-dreambooth", private=True) +RepoUrl('https://huggingface.co/spaces/nateraw/my-dreambooth',...) + +#### edit\_discussion\_comment + +[](#huggingface_hub.HfApi.edit_discussion_comment)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L6808) + +( repo\_id: strdiscussion\_num: intcomment\_id: strnew\_content: strtoken: Union\[bool, str, None\] = Nonerepo\_type: Optional\[str\] = None ) → export const metadata = 'undefined';[DiscussionComment](/docs/huggingface_hub/v0.30.2/en/package_reference/community#huggingface_hub.DiscussionComment) + +Parameters + +* [](#huggingface_hub.HfApi.edit_discussion_comment.repo_id)**repo\_id** (`str`) — A namespace (user or an organization) and a repo name separated by a `/`. +* [](#huggingface_hub.HfApi.edit_discussion_comment.discussion_num)**discussion\_num** (`int`) — The number of the Discussion or Pull Request . Must be a strictly positive integer. +* [](#huggingface_hub.HfApi.edit_discussion_comment.comment_id)**comment\_id** (`str`) — The ID of the comment to edit. +* [](#huggingface_hub.HfApi.edit_discussion_comment.new_content)**new\_content** (`str`) — The new content of the comment. Comments support markdown formatting. +* [](#huggingface_hub.HfApi.edit_discussion_comment.repo_type)**repo\_type** (`str`, _optional_) — Set to `"dataset"` or `"space"` if uploading to a dataset or space, `None` or `"model"` if uploading to a model. Default is `None`. +* [](#huggingface_hub.HfApi.edit_discussion_comment.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +[DiscussionComment](/docs/huggingface_hub/v0.30.2/en/package_reference/community#huggingface_hub.DiscussionComment) + +export const metadata = 'undefined'; + +the edited comment + +Edits a comment on a Discussion / Pull Request. + +Raises the following errors: + +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) if the HuggingFace API returned an error +* [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError) if some parameter value is invalid +* [RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) If the repository to download from cannot be found. This may be because it doesn’t exist, or because it is set to `private` and you do not have access. + +#### enable\_webhook + +[](#huggingface_hub.HfApi.enable_webhook)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L9270) + +( webhook\_id: strtoken: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';[WebhookInfo](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.WebhookInfo) + +Parameters + +* [](#huggingface_hub.HfApi.enable_webhook.webhook_id)**webhook\_id** (`str`) — The unique identifier of the webhook to enable. +* [](#huggingface_hub.HfApi.enable_webhook.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +[WebhookInfo](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.WebhookInfo) + +export const metadata = 'undefined'; + +Info about the enabled webhook. + +Enable a webhook (makes it “active”). + +[](#huggingface_hub.HfApi.enable_webhook.example) + +Example: + +Copied + +\>>> from huggingface\_hub import enable\_webhook +\>>> enabled\_webhook = enable\_webhook("654bbbc16f2ec14d77f109cc") +\>>> enabled\_webhook +WebhookInfo( + id\="654bbbc16f2ec14d77f109cc", + url="https://webhook.site/a2176e82-5720-43ee-9e06-f91cb4c91548", + watched=\[WebhookWatchedItem(type\="user", name="julien-c"), WebhookWatchedItem(type\="org", name="HuggingFaceH4")\], + domains=\["repo", "discussion"\], + secret="my-secret", + disabled=False, +) + +#### file\_exists + +[](#huggingface_hub.HfApi.file_exists)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L2907) + +( repo\_id: strfilename: strrepo\_type: Optional\[str\] = Nonerevision: Optional\[str\] = Nonetoken: Union\[str, bool, None\] = None ) + +Parameters + +* [](#huggingface_hub.HfApi.file_exists.repo_id)**repo\_id** (`str`) — A namespace (user or an organization) and a repo name separated by a `/`. +* [](#huggingface_hub.HfApi.file_exists.filename)**filename** (`str`) — The name of the file to check, for example: `"config.json"` +* [](#huggingface_hub.HfApi.file_exists.repo_type)**repo\_type** (`str`, _optional_) — Set to `"dataset"` or `"space"` if getting repository info from a dataset or a space, `None` or `"model"` if getting repository info from a model. Default is `None`. +* [](#huggingface_hub.HfApi.file_exists.revision)**revision** (`str`, _optional_) — The revision of the repository from which to get the information. Defaults to `"main"` branch. +* [](#huggingface_hub.HfApi.file_exists.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Checks if a file exists in a repository on the Hugging Face Hub. + +[](#huggingface_hub.HfApi.file_exists.example) + +Examples: + +Copied + +\>>> from huggingface\_hub import file\_exists +\>>> file\_exists("bigcode/starcoder", "config.json") +True +\>>> file\_exists("bigcode/starcoder", "not-a-file") +False +\>>> file\_exists("bigcode/not-a-repo", "config.json") +False + +#### get\_collection + +[](#huggingface_hub.HfApi.get_collection)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L8175) + +( collection\_slug: strtoken: Union\[bool, str, None\] = None ) + +Parameters + +* [](#huggingface_hub.HfApi.get_collection.collection_slug)**collection\_slug** (`str`) — Slug of the collection of the Hub. Example: `"TheBloke/recent-models-64f9a55bb3115b4f513ec026"`. +* [](#huggingface_hub.HfApi.get_collection.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Gets information about a Collection on the Hub. + +Returns: [Collection](/docs/huggingface_hub/v0.30.2/en/package_reference/collections#huggingface_hub.Collection) + +[](#huggingface_hub.HfApi.get_collection.example) + +Example: + +Copied + +\>>> from huggingface\_hub import get\_collection +\>>> collection = get\_collection("TheBloke/recent-models-64f9a55bb3115b4f513ec026") +\>>> collection.title +'Recent models' +\>>> len(collection.items) +37 +\>>> collection.items\[0\] +CollectionItem( + item\_object\_id='651446103cd773a050bf64c2', + item\_id='TheBloke/U-Amethyst-20B-AWQ', + item\_type='model', + position=88, + note=None +) + +#### get\_dataset\_tags + +[](#huggingface_hub.HfApi.get_dataset_tags)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L1802) + +( ) + +List all valid dataset tags as a nested namespace object. + +#### get\_discussion\_details + +[](#huggingface_hub.HfApi.get_discussion_details)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L6267) + +( repo\_id: strdiscussion\_num: intrepo\_type: Optional\[str\] = Nonetoken: Union\[bool, str, None\] = None ) + +Parameters + +* [](#huggingface_hub.HfApi.get_discussion_details.repo_id)**repo\_id** (`str`) — A namespace (user or an organization) and a repo name separated by a `/`. +* [](#huggingface_hub.HfApi.get_discussion_details.discussion_num)**discussion\_num** (`int`) — The number of the Discussion or Pull Request . Must be a strictly positive integer. +* [](#huggingface_hub.HfApi.get_discussion_details.repo_type)**repo\_type** (`str`, _optional_) — Set to `"dataset"` or `"space"` if uploading to a dataset or space, `None` or `"model"` if uploading to a model. Default is `None`. +* [](#huggingface_hub.HfApi.get_discussion_details.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Fetches a Discussion’s / Pull Request ‘s details from the Hub. + +Returns: [DiscussionWithDetails](/docs/huggingface_hub/v0.30.2/en/package_reference/community#huggingface_hub.DiscussionWithDetails) + +Raises the following errors: + +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) if the HuggingFace API returned an error +* [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError) if some parameter value is invalid +* [RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) If the repository to download from cannot be found. This may be because it doesn’t exist, or because it is set to `private` and you do not have access. + +#### get\_full\_repo\_name + +[](#huggingface_hub.HfApi.get_full_repo_name)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L6121) + +( model\_id: strorganization: Optional\[str\] = Nonetoken: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';`str` + +Parameters + +* [](#huggingface_hub.HfApi.get_full_repo_name.model_id)**model\_id** (`str`) — The name of the model. +* [](#huggingface_hub.HfApi.get_full_repo_name.organization)**organization** (`str`, _optional_) — If passed, the repository name will be in the organization namespace instead of the user namespace. +* [](#huggingface_hub.HfApi.get_full_repo_name.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +`str` + +export const metadata = 'undefined'; + +The repository name in the user’s namespace ({username}/{model\_id}) if no organization is passed, and under the organization namespace ({organization}/{model\_id}) otherwise. + +Returns the repository name for a given model ID and optional organization. + +#### get\_hf\_file\_metadata + +[](#huggingface_hub.HfApi.get_hf_file_metadata)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L5317) + +( url: strtoken: Union\[bool, str, None\] = Noneproxies: Optional\[Dict\] = Nonetimeout: Optional\[float\] = 10 ) + +Parameters + +* [](#huggingface_hub.HfApi.get_hf_file_metadata.url)**url** (`str`) — File url, for example returned by [hf\_hub\_url()](/docs/huggingface_hub/v0.30.2/en/package_reference/file_download#huggingface_hub.hf_hub_url). +* [](#huggingface_hub.HfApi.get_hf_file_metadata.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. +* [](#huggingface_hub.HfApi.get_hf_file_metadata.proxies)**proxies** (`dict`, _optional_) — Dictionary mapping protocol to the URL of the proxy passed to `requests.request`. +* [](#huggingface_hub.HfApi.get_hf_file_metadata.timeout)**timeout** (`float`, _optional_, defaults to 10) — How many seconds to wait for the server to send metadata before giving up. + +Fetch metadata of a file versioned on the Hub for a given url. + +#### get\_inference\_endpoint + +[](#huggingface_hub.HfApi.get_inference_endpoint)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L7803) + +( name: strnamespace: Optional\[str\] = Nonetoken: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';[InferenceEndpoint](/docs/huggingface_hub/v0.30.2/en/package_reference/inference_endpoints#huggingface_hub.InferenceEndpoint) + +Parameters + +* [](#huggingface_hub.HfApi.get_inference_endpoint.name)**name** (`str`) — The name of the Inference Endpoint to retrieve information about. +* [](#huggingface_hub.HfApi.get_inference_endpoint.namespace)**namespace** (`str`, _optional_) — The namespace in which the Inference Endpoint is located. Defaults to the current user. +* [](#huggingface_hub.HfApi.get_inference_endpoint.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +[InferenceEndpoint](/docs/huggingface_hub/v0.30.2/en/package_reference/inference_endpoints#huggingface_hub.InferenceEndpoint) + +export const metadata = 'undefined'; + +information about the requested Inference Endpoint. + +Get information about an Inference Endpoint. + +[](#huggingface_hub.HfApi.get_inference_endpoint.example) + +Example: + +Copied + +\>>> from huggingface\_hub import HfApi +\>>> api = HfApi() +\>>> endpoint = api.get\_inference\_endpoint("my-text-to-image") +\>>> endpoint +InferenceEndpoint(name='my-text-to-image', ...) + +\# Get status +\>>> endpoint.status +'running' +\>>> endpoint.url +'https://my-text-to-image.region.vendor.endpoints.huggingface.cloud' + +\# Run inference +\>>> endpoint.client.text\_to\_image(...) + +#### get\_model\_tags + +[](#huggingface_hub.HfApi.get_model_tags)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L1793) + +( ) + +List all valid model tags as a nested namespace object + +#### get\_paths\_info + +[](#huggingface_hub.HfApi.get_paths_info)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L3292) + +( repo\_id: strpaths: Union\[List\[str\], str\]expand: bool = Falserevision: Optional\[str\] = Nonerepo\_type: Optional\[str\] = Nonetoken: Union\[str, bool, None\] = None ) → export const metadata = 'undefined';`List[Union[RepoFile, RepoFolder]]` + +Expand 6 parameters + +Parameters + +* [](#huggingface_hub.HfApi.get_paths_info.repo_id)**repo\_id** (`str`) — A namespace (user or an organization) and a repo name separated by a `/`. +* [](#huggingface_hub.HfApi.get_paths_info.paths)**paths** (`Union[List[str], str]`, _optional_) — The paths to get information about. If a path do not exist, it is ignored without raising an exception. +* [](#huggingface_hub.HfApi.get_paths_info.expand)**expand** (`bool`, _optional_, defaults to `False`) — Whether to fetch more information about the paths (e.g. last commit and files’ security scan results). This operation is more expensive for the server so only 50 results are returned per page (instead of 1000). As pagination is implemented in `huggingface_hub`, this is transparent for you except for the time it takes to get the results. +* [](#huggingface_hub.HfApi.get_paths_info.revision)**revision** (`str`, _optional_) — The revision of the repository from which to get the information. Defaults to `"main"` branch. +* [](#huggingface_hub.HfApi.get_paths_info.repo_type)**repo\_type** (`str`, _optional_) — The type of the repository from which to get the information (`"model"`, `"dataset"` or `"space"`. Defaults to `"model"`. +* [](#huggingface_hub.HfApi.get_paths_info.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +`List[Union[RepoFile, RepoFolder]]` + +export const metadata = 'undefined'; + +The information about the paths, as a list of `RepoFile` and `RepoFolder` objects. + +Raises + +export const metadata = 'undefined'; + +[RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) or [RevisionNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RevisionNotFoundError) + +export const metadata = 'undefined'; + +* [RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) — If repository is not found (error 404): wrong repo\_id/repo\_type, private but not authenticated or repo does not exist. +* [RevisionNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RevisionNotFoundError) — If revision is not found (error 404) on the repo. + +Get information about a repo’s paths. + +[](#huggingface_hub.HfApi.get_paths_info.example) + +Example: + +Copied + +\>>> from huggingface\_hub import get\_paths\_info +\>>> paths\_info = get\_paths\_info("allenai/c4", \["README.md", "en"\], repo\_type="dataset") +\>>> paths\_info +\[ + RepoFile(path='README.md', size=2379, blob\_id='f84cb4c97182890fc1dbdeaf1a6a468fd27b4fff', lfs=None, last\_commit=None, security=None), + RepoFolder(path='en', tree\_id='dc943c4c40f53d02b31ced1defa7e5f438d5862e', last\_commit=None) +\] + +#### get\_repo\_discussions + +[](#huggingface_hub.HfApi.get_repo_discussions)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L6159) + +( repo\_id: strauthor: Optional\[str\] = Nonediscussion\_type: Optional\[constants.DiscussionTypeFilter\] = Nonediscussion\_status: Optional\[constants.DiscussionStatusFilter\] = Nonerepo\_type: Optional\[str\] = Nonetoken: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';`Iterator[Discussion]` + +Expand 6 parameters + +Parameters + +* [](#huggingface_hub.HfApi.get_repo_discussions.repo_id)**repo\_id** (`str`) — A namespace (user or an organization) and a repo name separated by a `/`. +* [](#huggingface_hub.HfApi.get_repo_discussions.author)**author** (`str`, _optional_) — Pass a value to filter by discussion author. `None` means no filter. Default is `None`. +* [](#huggingface_hub.HfApi.get_repo_discussions.discussion_type)**discussion\_type** (`str`, _optional_) — Set to `"pull_request"` to fetch only pull requests, `"discussion"` to fetch only discussions. Set to `"all"` or `None` to fetch both. Default is `None`. +* [](#huggingface_hub.HfApi.get_repo_discussions.discussion_status)**discussion\_status** (`str`, _optional_) — Set to `"open"` (respectively `"closed"`) to fetch only open (respectively closed) discussions. Set to `"all"` or `None` to fetch both. Default is `None`. +* [](#huggingface_hub.HfApi.get_repo_discussions.repo_type)**repo\_type** (`str`, _optional_) — Set to `"dataset"` or `"space"` if fetching from a dataset or space, `None` or `"model"` if fetching from a model. Default is `None`. +* [](#huggingface_hub.HfApi.get_repo_discussions.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +`Iterator[Discussion]` + +export const metadata = 'undefined'; + +An iterator of [Discussion](/docs/huggingface_hub/v0.30.2/en/package_reference/community#huggingface_hub.Discussion) objects. + +Fetches Discussions and Pull Requests for the given repo. + +Example: + +[](#huggingface_hub.HfApi.get_repo_discussions.example) + +Collecting all discussions of a repo in a list: + +Copied + +\>>> from huggingface\_hub import get\_repo\_discussions +\>>> discussions\_list = list(get\_repo\_discussions(repo\_id="bert-base-uncased")) + +[](#huggingface_hub.HfApi.get_repo_discussions.example-2) + +Iterating over discussions of a repo: + +Copied + +\>>> from huggingface\_hub import get\_repo\_discussions +\>>> for discussion in get\_repo\_discussions(repo\_id="bert-base-uncased"): +... print(discussion.num, discussion.title) + +#### get\_safetensors\_metadata + +[](#huggingface_hub.HfApi.get_safetensors_metadata)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L5623) + +( repo\_id: strrepo\_type: Optional\[str\] = Nonerevision: Optional\[str\] = Nonetoken: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';`SafetensorsRepoMetadata` + +Expand 4 parameters + +Parameters + +* [](#huggingface_hub.HfApi.get_safetensors_metadata.repo_id)**repo\_id** (`str`) — A user or an organization name and a repo name separated by a `/`. +* [](#huggingface_hub.HfApi.get_safetensors_metadata.repo_type)**repo\_type** (`str`, _optional_) — Set to `"dataset"` or `"space"` if the file is in a dataset or space, `None` or `"model"` if in a model. Default is `None`. +* [](#huggingface_hub.HfApi.get_safetensors_metadata.revision)**revision** (`str`, _optional_) — The git revision to fetch the file from. Can be a branch name, a tag, or a commit hash. Defaults to the head of the `"main"` branch. +* [](#huggingface_hub.HfApi.get_safetensors_metadata.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +`SafetensorsRepoMetadata` + +export const metadata = 'undefined'; + +information related to safetensors repo. + +Raises + +export const metadata = 'undefined'; + +`NotASafetensorsRepoError` or `SafetensorsParsingError` + +export const metadata = 'undefined'; + +* `NotASafetensorsRepoError` — If the repo is not a safetensors repo i.e. doesn’t have either a `model.safetensors` or a `model.safetensors.index.json` file. +* `SafetensorsParsingError` — If a safetensors file header couldn’t be parsed correctly. + +Parse metadata for a safetensors repo on the Hub. + +We first check if the repo has a single safetensors file or a sharded safetensors repo. If it’s a single safetensors file, we parse the metadata from this file. If it’s a sharded safetensors repo, we parse the metadata from the index file and then parse the metadata from each shard. + +To parse metadata from a single safetensors file, use [parse\_safetensors\_file\_metadata()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.parse_safetensors_file_metadata). + +For more details regarding the safetensors format, check out [https://huggingface.co/docs/safetensors/index#format](https://huggingface.co/docs/safetensors/index#format). + +[](#huggingface_hub.HfApi.get_safetensors_metadata.example) + +Example: + +Copied + +\# Parse repo with single weights file +\>>> metadata = get\_safetensors\_metadata("bigscience/bloomz-560m") +\>>> metadata +SafetensorsRepoMetadata( + metadata=None, + sharded=False, + weight\_map={'h.0.input\_layernorm.bias': 'model.safetensors', ...}, + files\_metadata={'model.safetensors': SafetensorsFileMetadata(...)} +) +\>>> metadata.files\_metadata\["model.safetensors"\].metadata +{'format': 'pt'} + +\# Parse repo with sharded model +\>>> metadata = get\_safetensors\_metadata("bigscience/bloom") +Parse safetensors files: 100%|██████████████████████████████████████████| 72/72 \[00:12<00:00, 5.78it/s\] +\>>> metadata +SafetensorsRepoMetadata(metadata={'total\_size': 352494542848}, sharded=True, weight\_map={...}, files\_metadata={...}) +\>>> len(metadata.files\_metadata) +72 \# All safetensors files have been fetched + +\# Parse repo with sharded model +\>>> get\_safetensors\_metadata("runwayml/stable-diffusion-v1-5") +NotASafetensorsRepoError: 'runwayml/stable-diffusion-v1-5' is not a safetensors repo. Couldn't find 'model.safetensors.index.json' or 'model.safetensors' files. + +#### get\_space\_runtime + +[](#huggingface_hub.HfApi.get_space_runtime)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L7089) + +( repo\_id: strtoken: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';[SpaceRuntime](/docs/huggingface_hub/v0.30.2/en/package_reference/space_runtime#huggingface_hub.SpaceRuntime) + +Parameters + +* [](#huggingface_hub.HfApi.get_space_runtime.repo_id)**repo\_id** (`str`) — ID of the repo to update. Example: `"bigcode/in-the-stack"`. +* [](#huggingface_hub.HfApi.get_space_runtime.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +[SpaceRuntime](/docs/huggingface_hub/v0.30.2/en/package_reference/space_runtime#huggingface_hub.SpaceRuntime) + +export const metadata = 'undefined'; + +Runtime information about a Space including Space stage and hardware. + +Gets runtime information about a Space. + +#### get\_space\_variables + +[](#huggingface_hub.HfApi.get_space_variables)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L6997) + +( repo\_id: strtoken: Union\[bool, str, None\] = None ) + +Parameters + +* [](#huggingface_hub.HfApi.get_space_variables.repo_id)**repo\_id** (`str`) — ID of the repo to query. Example: `"bigcode/in-the-stack"`. +* [](#huggingface_hub.HfApi.get_space_variables.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Gets all variables from a Space. + +Variables allow to set environment variables to a Space without hardcoding them. For more details, see [https://huggingface.co/docs/hub/spaces-overview#managing-secrets-and-environment-variables](https://huggingface.co/docs/hub/spaces-overview#managing-secrets-and-environment-variables) + +#### get\_token\_permission + +[](#huggingface_hub.HfApi.get_token_permission)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L1753) + +( token: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';`Literal["read", "write", "fineGrained", None]` + +Parameters + +* [](#huggingface_hub.HfApi.get_token_permission.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +`Literal["read", "write", "fineGrained", None]` + +export const metadata = 'undefined'; + +Permission granted by the token (“read” or “write”). Returns `None` if no token passed, if token is invalid or if role is not returned by the server. This typically happens when the token is an OAuth token. + +Check if a given `token` is valid and return its permissions. + +This method is deprecated and will be removed in version 1.0. Permissions are more complex than when `get_token_permission` was first introduced. OAuth and fine-grain tokens allows for more detailed permissions. If you need to know the permissions associated with a token, please use `whoami` and check the `'auth'` key. + +For more details about tokens, please refer to [https://huggingface.co/docs/hub/security-tokens#what-are-user-access-tokens](https://huggingface.co/docs/hub/security-tokens#what-are-user-access-tokens). + +#### get\_user\_overview + +[](#huggingface_hub.HfApi.get_user_overview)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L9571) + +( username: strtoken: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';`User` + +Parameters + +* [](#huggingface_hub.HfApi.get_user_overview.username)**username** (`str`) — Username of the user to get an overview of. +* [](#huggingface_hub.HfApi.get_user_overview.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +`User` + +export const metadata = 'undefined'; + +A [User](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.User) object with the user’s overview. + +Raises + +export const metadata = 'undefined'; + +`HTTPError` + +export const metadata = 'undefined'; + +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) — HTTP 404 If the user does not exist on the Hub. + +Get an overview of a user on the Hub. + +#### get\_webhook + +[](#huggingface_hub.HfApi.get_webhook)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L9017) + +( webhook\_id: strtoken: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';[WebhookInfo](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.WebhookInfo) + +Parameters + +* [](#huggingface_hub.HfApi.get_webhook.webhook_id)**webhook\_id** (`str`) — The unique identifier of the webhook to get. +* [](#huggingface_hub.HfApi.get_webhook.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +[WebhookInfo](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.WebhookInfo) + +export const metadata = 'undefined'; + +Info about the webhook. + +Get a webhook by its id. + +[](#huggingface_hub.HfApi.get_webhook.example) + +Example: + +Copied + +\>>> from huggingface\_hub import get\_webhook +\>>> webhook = get\_webhook("654bbbc16f2ec14d77f109cc") +\>>> print(webhook) +WebhookInfo( + id\="654bbbc16f2ec14d77f109cc", + watched=\[WebhookWatchedItem(type\="user", name="julien-c"), WebhookWatchedItem(type\="org", name="HuggingFaceH4")\], + url="https://webhook.site/a2176e82-5720-43ee-9e06-f91cb4c91548", + secret="my-secret", + domains=\["repo", "discussion"\], + disabled=False, +) + +#### grant\_access + +[](#huggingface_hub.HfApi.grant_access)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L8962) + +( repo\_id: struser: strrepo\_type: Optional\[str\] = Nonetoken: Union\[bool, str, None\] = None ) + +Expand 4 parameters + +Parameters + +* [](#huggingface_hub.HfApi.grant_access.repo_id)**repo\_id** (`str`) — The id of the repo to grant access to. +* [](#huggingface_hub.HfApi.grant_access.user)**user** (`str`) — The username of the user to grant access. +* [](#huggingface_hub.HfApi.grant_access.repo_type)**repo\_type** (`str`, _optional_) — The type of the repo to grant access to. Must be one of `model`, `dataset` or `space`. Defaults to `model`. +* [](#huggingface_hub.HfApi.grant_access.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Raises + +export const metadata = 'undefined'; + +`HTTPError` + +export const metadata = 'undefined'; + +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) — HTTP 400 if the repo is not gated. +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) — HTTP 400 if the user already has access to the repo. +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) — HTTP 403 if you only have read-only access to the repo. This can be the case if you don’t have `write` or `admin` role in the organization the repo belongs to or if you passed a `read` token. +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) — HTTP 404 if the user does not exist on the Hub. + +Grant access to a user for a given gated repo. + +Granting access don’t require for the user to send an access request by themselves. The user is automatically added to the accepted list meaning they can download the files You can revoke the granted access at any time using [cancel\_access\_request()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.cancel_access_request) or [reject\_access\_request()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.reject_access_request). + +For more info about gated repos, see [https://huggingface.co/docs/hub/models-gated](https://huggingface.co/docs/hub/models-gated). + +#### hf\_hub\_download + +[](#huggingface_hub.HfApi.hf_hub_download)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L5358) + +( repo\_id: strfilename: strsubfolder: Optional\[str\] = Nonerepo\_type: Optional\[str\] = Nonerevision: Optional\[str\] = Nonecache\_dir: Union\[str, Path, None\] = Nonelocal\_dir: Union\[str, Path, None\] = Noneforce\_download: bool = Falseproxies: Optional\[Dict\] = Noneetag\_timeout: float = 10token: Union\[bool, str, None\] = Nonelocal\_files\_only: bool = Falseresume\_download: Optional\[bool\] = Noneforce\_filename: Optional\[str\] = Nonelocal\_dir\_use\_symlinks: Union\[bool, Literal\['auto'\]\] = 'auto' ) → export const metadata = 'undefined';`str` + +Expand 12 parameters + +Parameters + +* [](#huggingface_hub.HfApi.hf_hub_download.repo_id)**repo\_id** (`str`) — A user or an organization name and a repo name separated by a `/`. +* [](#huggingface_hub.HfApi.hf_hub_download.filename)**filename** (`str`) — The name of the file in the repo. +* [](#huggingface_hub.HfApi.hf_hub_download.subfolder)**subfolder** (`str`, _optional_) — An optional value corresponding to a folder inside the repository. +* [](#huggingface_hub.HfApi.hf_hub_download.repo_type)**repo\_type** (`str`, _optional_) — Set to `"dataset"` or `"space"` if downloading from a dataset or space, `None` or `"model"` if downloading from a model. Default is `None`. +* [](#huggingface_hub.HfApi.hf_hub_download.revision)**revision** (`str`, _optional_) — An optional Git revision id which can be a branch name, a tag, or a commit hash. +* [](#huggingface_hub.HfApi.hf_hub_download.cache_dir)**cache\_dir** (`str`, `Path`, _optional_) — Path to the folder where cached files are stored. +* [](#huggingface_hub.HfApi.hf_hub_download.local_dir)**local\_dir** (`str` or `Path`, _optional_) — If provided, the downloaded file will be placed under this directory. +* [](#huggingface_hub.HfApi.hf_hub_download.force_download)**force\_download** (`bool`, _optional_, defaults to `False`) — Whether the file should be downloaded even if it already exists in the local cache. +* [](#huggingface_hub.HfApi.hf_hub_download.proxies)**proxies** (`dict`, _optional_) — Dictionary mapping protocol to the URL of the proxy passed to `requests.request`. +* [](#huggingface_hub.HfApi.hf_hub_download.etag_timeout)**etag\_timeout** (`float`, _optional_, defaults to `10`) — When fetching ETag, how many seconds to wait for the server to send data before giving up which is passed to `requests.request`. +* [](#huggingface_hub.HfApi.hf_hub_download.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. +* [](#huggingface_hub.HfApi.hf_hub_download.local_files_only)**local\_files\_only** (`bool`, _optional_, defaults to `False`) — If `True`, avoid downloading the file and return the path to the local cached file if it exists. + +Returns + +export const metadata = 'undefined'; + +`str` + +export const metadata = 'undefined'; + +Local path of file or if networking is off, last version of file cached on disk. + +Raises + +export const metadata = 'undefined'; + +[RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) or [RevisionNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RevisionNotFoundError) or [EntryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.EntryNotFoundError) or [LocalEntryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.LocalEntryNotFoundError) or `EnvironmentError` or `OSError` or `ValueError` + +export const metadata = 'undefined'; + +* [RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) — If the repository to download from cannot be found. This may be because it doesn’t exist, or because it is set to `private` and you do not have access. +* [RevisionNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RevisionNotFoundError) — If the revision to download from cannot be found. +* [EntryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.EntryNotFoundError) — If the file to download cannot be found. +* [LocalEntryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.LocalEntryNotFoundError) — If network is disabled or unavailable and file is not found in cache. +* [`EnvironmentError`](https://docs.python.org/3/library/exceptions.html#EnvironmentError) — If `token=True` but the token cannot be found. +* [`OSError`](https://docs.python.org/3/library/exceptions.html#OSError) — If ETag cannot be determined. +* [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError) — If some parameter value is invalid. + +Download a given file if it’s not already present in the local cache. + +The new cache file layout looks like this: + +* The cache directory contains one subfolder per repo\_id (namespaced by repo type) +* inside each repo folder: + * refs is a list of the latest known revision => commit\_hash pairs + * blobs contains the actual file blobs (identified by their git-sha or sha256, depending on whether they’re LFS files or not) + * snapshots contains one subfolder per commit, each “commit” contains the subset of the files that have been resolved at that particular commit. Each filename is a symlink to the blob at that particular commit. + +[](#huggingface_hub.HfApi.hf_hub_download.example) + +Copied + +\[ 96\] . +└── \[ 160\] models\--julien-c--EsperBERTo-small + ├── \[ 160\] blobs + │ ├── \[321M\] 403450e234d65943a7dcf7e05a771ce3c92faa84dd07db4ac20f592037a1e4bd + │ ├── \[ 398\] 7cb18dc9bafbfcf74629a4b760af1b160957a83e + │ └── \[1.4K\] d7edf6bd2a681fb0175f7735299831ee1b22b812 + ├── \[ 96\] refs + │ └── \[ 40\] main + └── \[ 128\] snapshots + ├── \[ 128\] 2439f60ef33a0d46d85da5001d52aeda5b00ce9f + │ ├── \[ 52\] README.md -> ../../blobs/d7edf6bd2a681fb0175f7735299831ee1b22b812 + │ └── \[ 76\] pytorch\_model.bin -> ../../blobs/403450e234d65943a7dcf7e05a771ce3c92faa84dd07db4ac20f592037a1e4bd + └── \[ 128\] bbc77c8132af1cc5cf678da3f1ddf2de43606d48 + ├── \[ 52\] README.md -> ../../blobs/7cb18dc9bafbfcf74629a4b760af1b160957a83e + └── \[ 76\] pytorch\_model.bin -> ../../blobs/403450e234d65943a7dcf7e05a771ce3c92faa84dd07db4ac20f592037a1e4bd + +If `local_dir` is provided, the file structure from the repo will be replicated in this location. When using this option, the `cache_dir` will not be used and a `.cache/huggingface/` folder will be created at the root of `local_dir` to store some metadata related to the downloaded files. While this mechanism is not as robust as the main cache-system, it’s optimized for regularly pulling the latest version of a repository. + +#### hide\_discussion\_comment + +[](#huggingface_hub.HfApi.hide_discussion_comment)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L6868) + +( repo\_id: strdiscussion\_num: intcomment\_id: strtoken: Union\[bool, str, None\] = Nonerepo\_type: Optional\[str\] = None ) → export const metadata = 'undefined';[DiscussionComment](/docs/huggingface_hub/v0.30.2/en/package_reference/community#huggingface_hub.DiscussionComment) + +Parameters + +* [](#huggingface_hub.HfApi.hide_discussion_comment.repo_id)**repo\_id** (`str`) — A namespace (user or an organization) and a repo name separated by a `/`. +* [](#huggingface_hub.HfApi.hide_discussion_comment.discussion_num)**discussion\_num** (`int`) — The number of the Discussion or Pull Request . Must be a strictly positive integer. +* [](#huggingface_hub.HfApi.hide_discussion_comment.comment_id)**comment\_id** (`str`) — The ID of the comment to edit. +* [](#huggingface_hub.HfApi.hide_discussion_comment.repo_type)**repo\_type** (`str`, _optional_) — Set to `"dataset"` or `"space"` if uploading to a dataset or space, `None` or `"model"` if uploading to a model. Default is `None`. +* [](#huggingface_hub.HfApi.hide_discussion_comment.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +[DiscussionComment](/docs/huggingface_hub/v0.30.2/en/package_reference/community#huggingface_hub.DiscussionComment) + +export const metadata = 'undefined'; + +the hidden comment + +Hides a comment on a Discussion / Pull Request. + +Hidden comments' content cannot be retrieved anymore. Hiding a comment is irreversible. + +Raises the following errors: + +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) if the HuggingFace API returned an error +* [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError) if some parameter value is invalid +* [RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) If the repository to download from cannot be found. This may be because it doesn’t exist, or because it is set to `private` and you do not have access. + +#### list\_accepted\_access\_requests + +[](#huggingface_hub.HfApi.list_accepted_access_requests)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L8646) + +( repo\_id: strrepo\_type: Optional\[str\] = Nonetoken: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';`List[AccessRequest]` + +Expand 3 parameters + +Parameters + +* [](#huggingface_hub.HfApi.list_accepted_access_requests.repo_id)**repo\_id** (`str`) — The id of the repo to get access requests for. +* [](#huggingface_hub.HfApi.list_accepted_access_requests.repo_type)**repo\_type** (`str`, _optional_) — The type of the repo to get access requests for. Must be one of `model`, `dataset` or `space`. Defaults to `model`. +* [](#huggingface_hub.HfApi.list_accepted_access_requests.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +`List[AccessRequest]` + +export const metadata = 'undefined'; + +A list of `AccessRequest` objects. Each time contains a `username`, `email`, `status` and `timestamp` attribute. If the gated repo has a custom form, the `fields` attribute will be populated with user’s answers. + +Raises + +export const metadata = 'undefined'; + +`HTTPError` + +export const metadata = 'undefined'; + +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) — HTTP 400 if the repo is not gated. +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) — HTTP 403 if you only have read-only access to the repo. This can be the case if you don’t have `write` or `admin` role in the organization the repo belongs to or if you passed a `read` token. + +Get accepted access requests for a given gated repo. + +An accepted request means the user has requested access to the repo and the request has been accepted. The user can download any file of the repo. If the approval mode is automatic, this list should contains by default all requests. Accepted requests can be cancelled or rejected at any time using [cancel\_access\_request()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.cancel_access_request) and [reject\_access\_request()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.reject_access_request). A cancelled request will go back to the pending list while a rejected request will go to the rejected list. In both cases, the user will lose access to the repo. + +For more info about gated repos, see [https://huggingface.co/docs/hub/models-gated](https://huggingface.co/docs/hub/models-gated). + +[](#huggingface_hub.HfApi.list_accepted_access_requests.example) + +Example: + +Copied + +\>>> from huggingface\_hub import list\_accepted\_access\_requests + +\>>> requests = list\_accepted\_access\_requests("meta-llama/Llama-2-7b") +\>>> len(requests) +411 +\>>> requests\[0\] +\[ + AccessRequest( + username='clem', + fullname='Clem 🤗', + email='\*\*\*', + timestamp=datetime.datetime(2023, 11, 23, 18, 4, 53, 828000, tzinfo=datetime.timezone.utc), + status='accepted', + fields=None, + ), + ... +\] + +#### list\_collections + +[](#huggingface_hub.HfApi.list_collections)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L8116) + +( owner: Union\[List\[str\], str, None\] = Noneitem: Union\[List\[str\], str, None\] = Nonesort: Optional\[Literal\['lastModified', 'trending', 'upvotes'\]\] = Nonelimit: Optional\[int\] = Nonetoken: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';`Iterable[Collection]` + +Parameters + +* [](#huggingface_hub.HfApi.list_collections.owner)**owner** (`List[str]` or `str`, _optional_) — Filter by owner’s username. +* [](#huggingface_hub.HfApi.list_collections.item)**item** (`List[str]` or `str`, _optional_) — Filter collections containing a particular items. Example: `"models/teknium/OpenHermes-2.5-Mistral-7B"`, `"datasets/squad"` or `"papers/2311.12983"`. +* [](#huggingface_hub.HfApi.list_collections.sort)**sort** (`Literal["lastModified", "trending", "upvotes"]`, _optional_) — Sort collections by last modified, trending or upvotes. +* [](#huggingface_hub.HfApi.list_collections.limit)**limit** (`int`, _optional_) — Maximum number of collections to be returned. +* [](#huggingface_hub.HfApi.list_collections.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +`Iterable[Collection]` + +export const metadata = 'undefined'; + +an iterable of [Collection](/docs/huggingface_hub/v0.30.2/en/package_reference/collections#huggingface_hub.Collection) objects. + +List collections on the Huggingface Hub, given some filters. + +When listing collections, the item list per collection is truncated to 4 items maximum. To retrieve all items from a collection, you must use [get\_collection()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.get_collection). + +#### list\_datasets + +[](#huggingface_hub.HfApi.list_datasets)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L2031) + +( filter: Union\[str, Iterable\[str\], None\] = Noneauthor: Optional\[str\] = Nonebenchmark: Optional\[Union\[str, List\[str\]\]\] = Nonedataset\_name: Optional\[str\] = Nonegated: Optional\[bool\] = Nonelanguage\_creators: Optional\[Union\[str, List\[str\]\]\] = Nonelanguage: Optional\[Union\[str, List\[str\]\]\] = Nonemultilinguality: Optional\[Union\[str, List\[str\]\]\] = Nonesize\_categories: Optional\[Union\[str, List\[str\]\]\] = Nonetags: Optional\[Union\[str, List\[str\]\]\] = Nonetask\_categories: Optional\[Union\[str, List\[str\]\]\] = Nonetask\_ids: Optional\[Union\[str, List\[str\]\]\] = Nonesearch: Optional\[str\] = Nonesort: Optional\[Union\[Literal\['last\_modified'\], str\]\] = Nonedirection: Optional\[Literal\[-1\]\] = Nonelimit: Optional\[int\] = Noneexpand: Optional\[List\[ExpandDatasetProperty\_T\]\] = Nonefull: Optional\[bool\] = Nonetoken: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';`Iterable[DatasetInfo]` + +Expand 19 parameters + +Parameters + +* [](#huggingface_hub.HfApi.list_datasets.filter)**filter** (`str` or `Iterable[str]`, _optional_) — A string or list of string to filter datasets on the hub. +* [](#huggingface_hub.HfApi.list_datasets.author)**author** (`str`, _optional_) — A string which identify the author of the returned datasets. +* [](#huggingface_hub.HfApi.list_datasets.benchmark)**benchmark** (`str` or `List`, _optional_) — A string or list of strings that can be used to identify datasets on the Hub by their official benchmark. +* [](#huggingface_hub.HfApi.list_datasets.dataset_name)**dataset\_name** (`str`, _optional_) — A string or list of strings that can be used to identify datasets on the Hub by its name, such as `SQAC` or `wikineural` +* [](#huggingface_hub.HfApi.list_datasets.gated)**gated** (`bool`, _optional_) — A boolean to filter datasets on the Hub that are gated or not. By default, all datasets are returned. If `gated=True` is passed, only gated datasets are returned. If `gated=False` is passed, only non-gated datasets are returned. +* [](#huggingface_hub.HfApi.list_datasets.language_creators)**language\_creators** (`str` or `List`, _optional_) — A string or list of strings that can be used to identify datasets on the Hub with how the data was curated, such as `crowdsourced` or `machine_generated`. +* [](#huggingface_hub.HfApi.list_datasets.language)**language** (`str` or `List`, _optional_) — A string or list of strings representing a two-character language to filter datasets by on the Hub. +* [](#huggingface_hub.HfApi.list_datasets.multilinguality)**multilinguality** (`str` or `List`, _optional_) — A string or list of strings representing a filter for datasets that contain multiple languages. +* [](#huggingface_hub.HfApi.list_datasets.size_categories)**size\_categories** (`str` or `List`, _optional_) — A string or list of strings that can be used to identify datasets on the Hub by the size of the dataset such as `100K>> from huggingface\_hub import HfApi + +\>>> api = HfApi() + +\# List all datasets +\>>> api.list\_datasets() + +\# List only the text classification datasets +\>>> api.list\_datasets(filter\="task\_categories:text-classification") + +\# List only the datasets in russian for language modeling +\>>> api.list\_datasets( +... filter\=("language:ru", "task\_ids:language-modeling") +... ) + +\# List FiftyOne datasets (identified by the tag "fiftyone" in dataset card) +\>>> api.list\_datasets(tags="fiftyone") + +[](#huggingface_hub.HfApi.list_datasets.example-2) + +Example usage with the `search` argument: + +Copied + +\>>> from huggingface\_hub import HfApi + +\>>> api = HfApi() + +\# List all datasets with "text" in their name +\>>> api.list\_datasets(search="text") + +\# List all datasets with "text" in their name made by google +\>>> api.list\_datasets(search="text", author="google") + +#### list\_inference\_catalog + +[](#huggingface_hub.HfApi.list_inference_catalog)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L7770) + +( token: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';List`str` + +Parameters + +* [](#huggingface_hub.HfApi.list_inference_catalog.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). + +Returns + +export const metadata = 'undefined'; + +List`str` + +export const metadata = 'undefined'; + +A list of model IDs available in the catalog. + +List models available in the Hugging Face Inference Catalog. + +The goal of the Inference Catalog is to provide a curated list of models that are optimized for inference and for which default configurations have been tested. See [https://endpoints.huggingface.co/catalog](https://endpoints.huggingface.co/catalog) for a list of available models in the catalog. + +Use [create\_inference\_endpoint\_from\_catalog()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.create_inference_endpoint_from_catalog) to deploy a model from the catalog. + +`list_inference_catalog` is experimental. Its API is subject to change in the future. Please provide feedback if you have any suggestions or requests. + +#### list\_inference\_endpoints + +[](#huggingface_hub.HfApi.list_inference_endpoints)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L7491) + +( namespace: Optional\[str\] = Nonetoken: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';List[InferenceEndpoint](/docs/huggingface_hub/v0.30.2/en/package_reference/inference_endpoints#huggingface_hub.InferenceEndpoint) + +Parameters + +* [](#huggingface_hub.HfApi.list_inference_endpoints.namespace)**namespace** (`str`, _optional_) — The namespace to list endpoints for. Defaults to the current user. Set to `"*"` to list all endpoints from all namespaces (i.e. personal namespace and all orgs the user belongs to). +* [](#huggingface_hub.HfApi.list_inference_endpoints.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +List[InferenceEndpoint](/docs/huggingface_hub/v0.30.2/en/package_reference/inference_endpoints#huggingface_hub.InferenceEndpoint) + +export const metadata = 'undefined'; + +A list of all inference endpoints for the given namespace. + +Lists all inference endpoints for the given namespace. + +[](#huggingface_hub.HfApi.list_inference_endpoints.example) + +Example: + +Copied + +\>>> from huggingface\_hub import HfApi +\>>> api = HfApi() +\>>> api.list\_inference\_endpoints() +\[InferenceEndpoint(name='my-endpoint', ...), ...\] + +#### list\_lfs\_files + +[](#huggingface_hub.HfApi.list_lfs_files)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L3455) + +( repo\_id: strrepo\_type: Optional\[str\] = Nonetoken: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';`Iterable[LFSFileInfo]` + +Parameters + +* [](#huggingface_hub.HfApi.list_lfs_files.repo_id)**repo\_id** (`str`) — The repository for which you are listing LFS files. +* [](#huggingface_hub.HfApi.list_lfs_files.repo_type)**repo\_type** (`str`, _optional_) — Type of repository. Set to `"dataset"` or `"space"` if listing from a dataset or space, `None` or `"model"` if listing from a model. Default is `None`. +* [](#huggingface_hub.HfApi.list_lfs_files.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +`Iterable[LFSFileInfo]` + +export const metadata = 'undefined'; + +An iterator of `LFSFileInfo` objects. + +List all LFS files in a repo on the Hub. + +This is primarily useful to count how much storage a repo is using and to eventually clean up large files with [permanently\_delete\_lfs\_files()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.permanently_delete_lfs_files). Note that this would be a permanent action that will affect all commits referencing this deleted files and that cannot be undone. + +[](#huggingface_hub.HfApi.list_lfs_files.example) + +Example: + +Copied + +\>>> from huggingface\_hub import HfApi +\>>> api = HfApi() +\>>> lfs\_files = api.list\_lfs\_files("username/my-cool-repo") + +\# Filter files files to delete based on a combination of \`filename\`, \`pushed\_at\`, \`ref\` or \`size\`. +\# e.g. select only LFS files in the "checkpoints" folder +\>>> lfs\_files\_to\_delete = (lfs\_file for lfs\_file in lfs\_files if lfs\_file.filename.startswith("checkpoints/")) + +\# Permanently delete LFS files +\>>> api.permanently\_delete\_lfs\_files("username/my-cool-repo", lfs\_files\_to\_delete) + +#### list\_liked\_repos + +[](#huggingface_hub.HfApi.list_liked_repos)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L2403) + +( user: Optional\[str\] = Nonetoken: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';[UserLikes](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.UserLikes) + +Parameters + +* [](#huggingface_hub.HfApi.list_liked_repos.user)**user** (`str`, _optional_) — Name of the user for which you want to fetch the likes. +* [](#huggingface_hub.HfApi.list_liked_repos.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +[UserLikes](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.UserLikes) + +export const metadata = 'undefined'; + +object containing the user name and 3 lists of repo ids (1 for models, 1 for datasets and 1 for Spaces). + +Raises + +export const metadata = 'undefined'; + +`ValueError` + +export const metadata = 'undefined'; + +* [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError) — If `user` is not passed and no token found (either from argument or from machine). + +List all public repos liked by a user on huggingface.co. + +This list is public so token is optional. If `user` is not passed, it defaults to the logged in user. + +See also [unlike()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.unlike). + +[](#huggingface_hub.HfApi.list_liked_repos.example) + +Example: + +Copied + +\>>> from huggingface\_hub import list\_liked\_repos + +\>>> likes = list\_liked\_repos("julien-c") + +\>>> likes.user +"julien-c" + +\>>> likes.models +\["osanseviero/streamlit\_1.15", "Xhaheen/ChatGPT\_HF", ...\] + +#### list\_models + +[](#huggingface_hub.HfApi.list_models)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L1811) + +( filter: Union\[str, Iterable\[str\], None\] = Noneauthor: Optional\[str\] = Nonegated: Optional\[bool\] = Noneinference: Optional\[Literal\['cold', 'frozen', 'warm'\]\] = Nonelibrary: Optional\[Union\[str, List\[str\]\]\] = Nonelanguage: Optional\[Union\[str, List\[str\]\]\] = Nonemodel\_name: Optional\[str\] = Nonetask: Optional\[Union\[str, List\[str\]\]\] = Nonetrained\_dataset: Optional\[Union\[str, List\[str\]\]\] = Nonetags: Optional\[Union\[str, List\[str\]\]\] = Nonesearch: Optional\[str\] = Nonepipeline\_tag: Optional\[str\] = Noneemissions\_thresholds: Optional\[Tuple\[float, float\]\] = Nonesort: Union\[Literal\['last\_modified'\], str, None\] = Nonedirection: Optional\[Literal\[-1\]\] = Nonelimit: Optional\[int\] = Noneexpand: Optional\[List\[ExpandModelProperty\_T\]\] = Nonefull: Optional\[bool\] = NonecardData: bool = Falsefetch\_config: bool = Falsetoken: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';`Iterable[ModelInfo]` + +Expand 21 parameters + +Parameters + +* [](#huggingface_hub.HfApi.list_models.filter)**filter** (`str` or `Iterable[str]`, _optional_) — A string or list of string to filter models on the Hub. +* [](#huggingface_hub.HfApi.list_models.author)**author** (`str`, _optional_) — A string which identify the author (user or organization) of the returned models. +* [](#huggingface_hub.HfApi.list_models.gated)**gated** (`bool`, _optional_) — A boolean to filter models on the Hub that are gated or not. By default, all models are returned. If `gated=True` is passed, only gated models are returned. If `gated=False` is passed, only non-gated models are returned. +* [](#huggingface_hub.HfApi.list_models.inference)**inference** (`Literal["cold", "frozen", "warm"]`, _optional_) — A string to filter models on the Hub by their state on the Inference API. Warm models are available for immediate use. Cold models will be loaded on first inference call. Frozen models are not available in Inference API. +* [](#huggingface_hub.HfApi.list_models.library)**library** (`str` or `List`, _optional_) — A string or list of strings of foundational libraries models were originally trained from, such as pytorch, tensorflow, or allennlp. +* [](#huggingface_hub.HfApi.list_models.language)**language** (`str` or `List`, _optional_) — A string or list of strings of languages, both by name and country code, such as “en” or “English” +* [](#huggingface_hub.HfApi.list_models.model_name)**model\_name** (`str`, _optional_) — A string that contain complete or partial names for models on the Hub, such as “bert” or “bert-base-cased” +* [](#huggingface_hub.HfApi.list_models.task)**task** (`str` or `List`, _optional_) — A string or list of strings of tasks models were designed for, such as: “fill-mask” or “automatic-speech-recognition” +* [](#huggingface_hub.HfApi.list_models.trained_dataset)**trained\_dataset** (`str` or `List`, _optional_) — A string tag or a list of string tags of the trained dataset for a model on the Hub. +* [](#huggingface_hub.HfApi.list_models.tags)**tags** (`str` or `List`, _optional_) — A string tag or a list of tags to filter models on the Hub by, such as `text-generation` or `spacy`. +* [](#huggingface_hub.HfApi.list_models.search)**search** (`str`, _optional_) — A string that will be contained in the returned model ids. +* [](#huggingface_hub.HfApi.list_models.pipeline_tag)**pipeline\_tag** (`str`, _optional_) — A string pipeline tag to filter models on the Hub by, such as `summarization`. +* [](#huggingface_hub.HfApi.list_models.emissions_thresholds)**emissions\_thresholds** (`Tuple`, _optional_) — A tuple of two ints or floats representing a minimum and maximum carbon footprint to filter the resulting models with in grams. +* [](#huggingface_hub.HfApi.list_models.sort)**sort** (`Literal["last_modified"]` or `str`, _optional_) — The key with which to sort the resulting models. Possible values are “last\_modified”, “trending\_score”, “created\_at”, “downloads” and “likes”. +* [](#huggingface_hub.HfApi.list_models.direction)**direction** (`Literal[-1]` or `int`, _optional_) — Direction in which to sort. The value `-1` sorts by descending order while all other values sort by ascending order. +* [](#huggingface_hub.HfApi.list_models.limit)**limit** (`int`, _optional_) — The limit on the number of models fetched. Leaving this option to `None` fetches all models. +* [](#huggingface_hub.HfApi.list_models.expand)**expand** (`List[ExpandModelProperty_T]`, _optional_) — List properties to return in the response. When used, only the properties in the list will be returned. This parameter cannot be used if `full`, `cardData` or `fetch_config` are passed. Possible values are `"author"`, `"baseModels"`, `"cardData"`, `"childrenModelCount"`, `"config"`, `"createdAt"`, `"disabled"`, `"downloads"`, `"downloadsAllTime"`, `"gated"`, `"gguf"`, `"inference"`, `"inferenceProviderMapping"`, `"lastModified"`, `"library_name"`, `"likes"`, `"mask_token"`, `"model-index"`, `"pipeline_tag"`, `"private"`, `"safetensors"`, `"sha"`, `"siblings"`, `"spaces"`, `"tags"`, `"transformersInfo"`, `"trendingScore"`, `"widgetData"`, `"usedStorage"`, `"resourceGroup"` and `"xetEnabled"`. +* [](#huggingface_hub.HfApi.list_models.full)**full** (`bool`, _optional_) — Whether to fetch all model data, including the `last_modified`, the `sha`, the files and the `tags`. This is set to `True` by default when using a filter. +* [](#huggingface_hub.HfApi.list_models.cardData)**cardData** (`bool`, _optional_) — Whether to grab the metadata for the model as well. Can contain useful information such as carbon emissions, metrics, and datasets trained on. +* [](#huggingface_hub.HfApi.list_models.fetch_config)**fetch\_config** (`bool`, _optional_) — Whether to fetch the model configs as well. This is not included in `full` due to its size. +* [](#huggingface_hub.HfApi.list_models.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +`Iterable[ModelInfo]` + +export const metadata = 'undefined'; + +an iterable of [huggingface\_hub.hf\_api.ModelInfo](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.ModelInfo) objects. + +List models hosted on the Huggingface Hub, given some filters. + +[](#huggingface_hub.HfApi.list_models.example) + +Example usage with the `filter` argument: + +Copied + +\>>> from huggingface\_hub import HfApi + +\>>> api = HfApi() + +\# List all models +\>>> api.list\_models() + +\# List only the text classification models +\>>> api.list\_models(filter\="text-classification") + +\# List only models from the AllenNLP library +\>>> api.list\_models(filter\="allennlp") + +[](#huggingface_hub.HfApi.list_models.example-2) + +Example usage with the `search` argument: + +Copied + +\>>> from huggingface\_hub import HfApi + +\>>> api = HfApi() + +\# List all models with "bert" in their name +\>>> api.list\_models(search="bert") + +\# List all models with "bert" in their name made by google +\>>> api.list\_models(search="bert", author="google") + +#### list\_organization\_members + +[](#huggingface_hub.HfApi.list_organization_members)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L9597) + +( organization: strtoken: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';`Iterable[User]` + +Parameters + +* [](#huggingface_hub.HfApi.list_organization_members.organization)**organization** (`str`) — Name of the organization to get the members of. +* [](#huggingface_hub.HfApi.list_organization_members.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +`Iterable[User]` + +export const metadata = 'undefined'; + +A list of [User](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.User) objects with the members of the organization. + +Raises + +export const metadata = 'undefined'; + +`HTTPError` + +export const metadata = 'undefined'; + +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) — HTTP 404 If the organization does not exist on the Hub. + +List of members of an organization on the Hub. + +#### list\_papers + +[](#huggingface_hub.HfApi.list_papers)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L9681) + +( query: Optional\[str\] = Nonetoken: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';`Iterable[PaperInfo]` + +Parameters + +* [](#huggingface_hub.HfApi.list_papers.query)**query** (`str`, _optional_) — A search query string to find papers. If provided, returns papers that match the query. +* [](#huggingface_hub.HfApi.list_papers.token)**token** (Union\[bool, str, None\], _optional_) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +`Iterable[PaperInfo]` + +export const metadata = 'undefined'; + +an iterable of `huggingface_hub.hf_api.PaperInfo` objects. + +List daily papers on the Hugging Face Hub given a search query. + +[](#huggingface_hub.HfApi.list_papers.example) + +Example: + +Copied + +\>>> from huggingface\_hub import HfApi + +\>>> api = HfApi() + +\# List all papers with "attention" in their title +\>>> api.list\_papers(query="attention") + +#### list\_pending\_access\_requests + +[](#huggingface_hub.HfApi.list_pending_access_requests)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L8582) + +( repo\_id: strrepo\_type: Optional\[str\] = Nonetoken: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';`List[AccessRequest]` + +Expand 3 parameters + +Parameters + +* [](#huggingface_hub.HfApi.list_pending_access_requests.repo_id)**repo\_id** (`str`) — The id of the repo to get access requests for. +* [](#huggingface_hub.HfApi.list_pending_access_requests.repo_type)**repo\_type** (`str`, _optional_) — The type of the repo to get access requests for. Must be one of `model`, `dataset` or `space`. Defaults to `model`. +* [](#huggingface_hub.HfApi.list_pending_access_requests.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +`List[AccessRequest]` + +export const metadata = 'undefined'; + +A list of `AccessRequest` objects. Each time contains a `username`, `email`, `status` and `timestamp` attribute. If the gated repo has a custom form, the `fields` attribute will be populated with user’s answers. + +Raises + +export const metadata = 'undefined'; + +`HTTPError` + +export const metadata = 'undefined'; + +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) — HTTP 400 if the repo is not gated. +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) — HTTP 403 if you only have read-only access to the repo. This can be the case if you don’t have `write` or `admin` role in the organization the repo belongs to or if you passed a `read` token. + +Get pending access requests for a given gated repo. + +A pending request means the user has requested access to the repo but the request has not been processed yet. If the approval mode is automatic, this list should be empty. Pending requests can be accepted or rejected using [accept\_access\_request()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.accept_access_request) and [reject\_access\_request()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.reject_access_request). + +For more info about gated repos, see [https://huggingface.co/docs/hub/models-gated](https://huggingface.co/docs/hub/models-gated). + +[](#huggingface_hub.HfApi.list_pending_access_requests.example) + +Example: + +Copied + +\>>> from huggingface\_hub import list\_pending\_access\_requests, accept\_access\_request + +\# List pending requests +\>>> requests = list\_pending\_access\_requests("meta-llama/Llama-2-7b") +\>>> len(requests) +411 +\>>> requests\[0\] +\[ + AccessRequest( + username='clem', + fullname='Clem 🤗', + email='\*\*\*', + timestamp=datetime.datetime(2023, 11, 23, 18, 4, 53, 828000, tzinfo=datetime.timezone.utc), + status='pending', + fields=None, + ), + ... +\] + +\# Accept Clem's request +\>>> accept\_access\_request("meta-llama/Llama-2-7b", "clem") + +#### list\_rejected\_access\_requests + +[](#huggingface_hub.HfApi.list_rejected_access_requests)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L8708) + +( repo\_id: strrepo\_type: Optional\[str\] = Nonetoken: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';`List[AccessRequest]` + +Expand 3 parameters + +Parameters + +* [](#huggingface_hub.HfApi.list_rejected_access_requests.repo_id)**repo\_id** (`str`) — The id of the repo to get access requests for. +* [](#huggingface_hub.HfApi.list_rejected_access_requests.repo_type)**repo\_type** (`str`, _optional_) — The type of the repo to get access requests for. Must be one of `model`, `dataset` or `space`. Defaults to `model`. +* [](#huggingface_hub.HfApi.list_rejected_access_requests.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +`List[AccessRequest]` + +export const metadata = 'undefined'; + +A list of `AccessRequest` objects. Each time contains a `username`, `email`, `status` and `timestamp` attribute. If the gated repo has a custom form, the `fields` attribute will be populated with user’s answers. + +Raises + +export const metadata = 'undefined'; + +`HTTPError` + +export const metadata = 'undefined'; + +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) — HTTP 400 if the repo is not gated. +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) — HTTP 403 if you only have read-only access to the repo. This can be the case if you don’t have `write` or `admin` role in the organization the repo belongs to or if you passed a `read` token. + +Get rejected access requests for a given gated repo. + +A rejected request means the user has requested access to the repo and the request has been explicitly rejected by a repo owner (either you or another user from your organization). The user cannot download any file of the repo. Rejected requests can be accepted or cancelled at any time using [accept\_access\_request()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.accept_access_request) and [cancel\_access\_request()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.cancel_access_request). A cancelled request will go back to the pending list while an accepted request will go to the accepted list. + +For more info about gated repos, see [https://huggingface.co/docs/hub/models-gated](https://huggingface.co/docs/hub/models-gated). + +[](#huggingface_hub.HfApi.list_rejected_access_requests.example) + +Example: + +Copied + +\>>> from huggingface\_hub import list\_rejected\_access\_requests + +\>>> requests = list\_rejected\_access\_requests("meta-llama/Llama-2-7b") +\>>> len(requests) +411 +\>>> requests\[0\] +\[ + AccessRequest( + username='clem', + fullname='Clem 🤗', + email='\*\*\*', + timestamp=datetime.datetime(2023, 11, 23, 18, 4, 53, 828000, tzinfo=datetime.timezone.utc), + status='rejected', + fields=None, + ), + ... +\] + +#### list\_repo\_commits + +[](#huggingface_hub.HfApi.list_repo_commits)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L3206) + +( repo\_id: strrepo\_type: Optional\[str\] = Nonetoken: Union\[bool, str, None\] = Nonerevision: Optional\[str\] = Noneformatted: bool = False ) → export const metadata = 'undefined';List\[[GitCommitInfo](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.GitCommitInfo)\] + +Expand 5 parameters + +Parameters + +* [](#huggingface_hub.HfApi.list_repo_commits.repo_id)**repo\_id** (`str`) — A namespace (user or an organization) and a repo name separated by a `/`. +* [](#huggingface_hub.HfApi.list_repo_commits.repo_type)**repo\_type** (`str`, _optional_) — Set to `"dataset"` or `"space"` if listing commits from a dataset or a Space, `None` or `"model"` if listing from a model. Default is `None`. +* [](#huggingface_hub.HfApi.list_repo_commits.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. +* [](#huggingface_hub.HfApi.list_repo_commits.revision)**revision** (`str`, _optional_) — The git revision to commit from. Defaults to the head of the `"main"` branch. +* [](#huggingface_hub.HfApi.list_repo_commits.formatted)**formatted** (`bool`) — Whether to return the HTML-formatted title and description of the commits. Defaults to False. + +Returns + +export const metadata = 'undefined'; + +List\[[GitCommitInfo](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.GitCommitInfo)\] + +export const metadata = 'undefined'; + +list of objects containing information about the commits for a repo on the Hub. + +Raises + +export const metadata = 'undefined'; + +[RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) or [RevisionNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RevisionNotFoundError) + +export const metadata = 'undefined'; + +* [RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) — If repository is not found (error 404): wrong repo\_id/repo\_type, private but not authenticated or repo does not exist. +* [RevisionNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RevisionNotFoundError) — If revision is not found (error 404) on the repo. + +Get the list of commits of a given revision for a repo on the Hub. + +Commits are sorted by date (last commit first). + +[](#huggingface_hub.HfApi.list_repo_commits.example) + +Example: + +Copied + +\>>> from huggingface\_hub import HfApi +\>>> api = HfApi() + +\# Commits are sorted by date (last commit first) +\>>> initial\_commit = api.list\_repo\_commits("gpt2")\[-1\] + +\# Initial commit is always a system commit containing the \`.gitattributes\` file. +\>>> initial\_commit +GitCommitInfo( + commit\_id='9b865efde13a30c13e0a33e536cf3e4a5a9d71d8', + authors=\['system'\], + created\_at=datetime.datetime(2019, 2, 18, 10, 36, 15, tzinfo=datetime.timezone.utc), + title='initial commit', + message='', + formatted\_title=None, + formatted\_message=None +) + +\# Create an empty branch by deriving from initial commit +\>>> api.create\_branch("gpt2", "new\_empty\_branch", revision=initial\_commit.commit\_id) + +#### list\_repo\_files + +[](#huggingface_hub.HfApi.list_repo_files)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L2965) + +( repo\_id: strrevision: Optional\[str\] = Nonerepo\_type: Optional\[str\] = Nonetoken: Union\[str, bool, None\] = None ) → export const metadata = 'undefined';`List[str]` + +Parameters + +* [](#huggingface_hub.HfApi.list_repo_files.repo_id)**repo\_id** (`str`) — A namespace (user or an organization) and a repo name separated by a `/`. +* [](#huggingface_hub.HfApi.list_repo_files.revision)**revision** (`str`, _optional_) — The revision of the repository from which to get the information. +* [](#huggingface_hub.HfApi.list_repo_files.repo_type)**repo\_type** (`str`, _optional_) — Set to `"dataset"` or `"space"` if uploading to a dataset or space, `None` or `"model"` if uploading to a model. Default is `None`. +* [](#huggingface_hub.HfApi.list_repo_files.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +`List[str]` + +export const metadata = 'undefined'; + +the list of files in a given repository. + +Get the list of files in a given repo. + +#### list\_repo\_likers + +[](#huggingface_hub.HfApi.list_repo_likers)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L2479) + +( repo\_id: strrepo\_type: Optional\[str\] = Nonetoken: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';`Iterable[User]` + +Parameters + +* [](#huggingface_hub.HfApi.list_repo_likers.repo_id)**repo\_id** (`str`) — The repository to retrieve . Example: `"user/my-cool-model"`. +* [](#huggingface_hub.HfApi.list_repo_likers.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. +* [](#huggingface_hub.HfApi.list_repo_likers.repo_type)**repo\_type** (`str`, _optional_) — Set to `"dataset"` or `"space"` if uploading to a dataset or space, `None` or `"model"` if uploading to a model. Default is `None`. + +Returns + +export const metadata = 'undefined'; + +`Iterable[User]` + +export const metadata = 'undefined'; + +an iterable of [huggingface\_hub.hf\_api.User](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.User) objects. + +List all users who liked a given repo on the hugging Face Hub. + +See also [list\_liked\_repos()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.list_liked_repos). + +#### list\_repo\_refs + +[](#huggingface_hub.HfApi.list_repo_refs)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L3134) + +( repo\_id: strrepo\_type: Optional\[str\] = Noneinclude\_pull\_requests: bool = Falsetoken: Union\[str, bool, None\] = None ) → export const metadata = 'undefined';[GitRefs](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.GitRefs) + +Parameters + +* [](#huggingface_hub.HfApi.list_repo_refs.repo_id)**repo\_id** (`str`) — A namespace (user or an organization) and a repo name separated by a `/`. +* [](#huggingface_hub.HfApi.list_repo_refs.repo_type)**repo\_type** (`str`, _optional_) — Set to `"dataset"` or `"space"` if listing refs from a dataset or a Space, `None` or `"model"` if listing from a model. Default is `None`. +* [](#huggingface_hub.HfApi.list_repo_refs.include_pull_requests)**include\_pull\_requests** (`bool`, _optional_) — Whether to include refs from pull requests in the list. Defaults to `False`. +* [](#huggingface_hub.HfApi.list_repo_refs.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +[GitRefs](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.GitRefs) + +export const metadata = 'undefined'; + +object containing all information about branches and tags for a repo on the Hub. + +Get the list of refs of a given repo (both tags and branches). + +[](#huggingface_hub.HfApi.list_repo_refs.example) + +Example: + +Copied + +\>>> from huggingface\_hub import HfApi +\>>> api = HfApi() +\>>> api.list\_repo\_refs("gpt2") +GitRefs(branches=\[GitRefInfo(name='main', ref='refs/heads/main', target\_commit='e7da7f221d5bf496a48136c0cd264e630fe9fcc8')\], converts=\[\], tags=\[\]) + +\>>> api.list\_repo\_refs("bigcode/the-stack", repo\_type='dataset') +GitRefs( + branches=\[ + GitRefInfo(name='main', ref='refs/heads/main', target\_commit='18edc1591d9ce72aa82f56c4431b3c969b210ae3'), + GitRefInfo(name='v1.1.a1', ref='refs/heads/v1.1.a1', target\_commit='f9826b862d1567f3822d3d25649b0d6d22ace714') + \], + converts=\[\], + tags=\[ + GitRefInfo(name='v1.0', ref='refs/tags/v1.0', target\_commit='c37a8cd1e382064d8aced5e05543c5f7753834da') + \] +) + +#### list\_repo\_tree + +[](#huggingface_hub.HfApi.list_repo_tree)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L3002) + +( repo\_id: strpath\_in\_repo: Optional\[str\] = Nonerecursive: bool = Falseexpand: bool = Falserevision: Optional\[str\] = Nonerepo\_type: Optional\[str\] = Nonetoken: Union\[str, bool, None\] = None ) → export const metadata = 'undefined';`Iterable[Union[RepoFile, RepoFolder]]` + +Expand 7 parameters + +Parameters + +* [](#huggingface_hub.HfApi.list_repo_tree.repo_id)**repo\_id** (`str`) — A namespace (user or an organization) and a repo name separated by a `/`. +* [](#huggingface_hub.HfApi.list_repo_tree.path_in_repo)**path\_in\_repo** (`str`, _optional_) — Relative path of the tree (folder) in the repo, for example: `"checkpoints/1fec34a/results"`. Will default to the root tree (folder) of the repository. +* [](#huggingface_hub.HfApi.list_repo_tree.recursive)**recursive** (`bool`, _optional_, defaults to `False`) — Whether to list tree’s files and folders recursively. +* [](#huggingface_hub.HfApi.list_repo_tree.expand)**expand** (`bool`, _optional_, defaults to `False`) — Whether to fetch more information about the tree’s files and folders (e.g. last commit and files’ security scan results). This operation is more expensive for the server so only 50 results are returned per page (instead of 1000). As pagination is implemented in `huggingface_hub`, this is transparent for you except for the time it takes to get the results. +* [](#huggingface_hub.HfApi.list_repo_tree.revision)**revision** (`str`, _optional_) — The revision of the repository from which to get the tree. Defaults to `"main"` branch. +* [](#huggingface_hub.HfApi.list_repo_tree.repo_type)**repo\_type** (`str`, _optional_) — The type of the repository from which to get the tree (`"model"`, `"dataset"` or `"space"`. Defaults to `"model"`. +* [](#huggingface_hub.HfApi.list_repo_tree.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +`Iterable[Union[RepoFile, RepoFolder]]` + +export const metadata = 'undefined'; + +The information about the tree’s files and folders, as an iterable of `RepoFile` and `RepoFolder` objects. The order of the files and folders is not guaranteed. + +Raises + +export const metadata = 'undefined'; + +[RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) or [RevisionNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RevisionNotFoundError) or [EntryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.EntryNotFoundError) + +export const metadata = 'undefined'; + +* [RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) — If repository is not found (error 404): wrong repo\_id/repo\_type, private but not authenticated or repo does not exist. +* [RevisionNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RevisionNotFoundError) — If revision is not found (error 404) on the repo. +* [EntryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.EntryNotFoundError) — If the tree (folder) does not exist (error 404) on the repo. + +List a repo tree’s files and folders and get information about them. + +Examples: + +Get information about a repo’s tree. + +[](#huggingface_hub.HfApi.list_repo_tree.example) + +Copied + +\>>> from huggingface\_hub import list\_repo\_tree +\>>> repo\_tree = list\_repo\_tree("lysandre/arxiv-nlp") +\>>> repo\_tree + +\>>> list(repo\_tree) +\[ + RepoFile(path='.gitattributes', size=391, blob\_id='ae8c63daedbd4206d7d40126955d4e6ab1c80f8f', lfs=None, last\_commit=None, security=None), + RepoFile(path='README.md', size=391, blob\_id='43bd404b159de6fba7c2f4d3264347668d43af25', lfs=None, last\_commit=None, security=None), + RepoFile(path='config.json', size=554, blob\_id='2f9618c3a19b9a61add74f70bfb121335aeef666', lfs=None, last\_commit=None, security=None), + RepoFile( + path='flax\_model.msgpack', size=497764107, blob\_id='8095a62ccb4d806da7666fcda07467e2d150218e', + lfs={'size': 497764107, 'sha256': 'd88b0d6a6ff9c3f8151f9d3228f57092aaea997f09af009eefd7373a77b5abb9', 'pointer\_size': 134}, last\_commit=None, security=None + ), + RepoFile(path='merges.txt', size=456318, blob\_id='226b0752cac7789c48f0cb3ec53eda48b7be36cc', lfs=None, last\_commit=None, security=None), + RepoFile( + path='pytorch\_model.bin', size=548123560, blob\_id='64eaa9c526867e404b68f2c5d66fd78e27026523', + lfs={'size': 548123560, 'sha256': '9be78edb5b928eba33aa88f431551348f7466ba9f5ef3daf1d552398722a5436', 'pointer\_size': 134}, last\_commit=None, security=None + ), + RepoFile(path='vocab.json', size=898669, blob\_id='b00361fece0387ca34b4b8b8539ed830d644dbeb', lfs=None, last\_commit=None, security=None)\] +\] + +Get even more information about a repo’s tree (last commit and files’ security scan results) + +[](#huggingface_hub.HfApi.list_repo_tree.example-2) + +Copied + +\>>> from huggingface\_hub import list\_repo\_tree +\>>> repo\_tree = list\_repo\_tree("prompthero/openjourney-v4", expand=True) +\>>> list(repo\_tree) +\[ + RepoFolder( + path='feature\_extractor', + tree\_id='aa536c4ea18073388b5b0bc791057a7296a00398', + last\_commit={ + 'oid': '47b62b20b20e06b9de610e840282b7e6c3d51190', + 'title': 'Upload diffusers weights (#48)', + 'date': datetime.datetime(2023, 3, 21, 9, 5, 27, tzinfo=datetime.timezone.utc) + } + ), + RepoFolder( + path='safety\_checker', + tree\_id='65aef9d787e5557373fdf714d6c34d4fcdd70440', + last\_commit={ + 'oid': '47b62b20b20e06b9de610e840282b7e6c3d51190', + 'title': 'Upload diffusers weights (#48)', + 'date': datetime.datetime(2023, 3, 21, 9, 5, 27, tzinfo=datetime.timezone.utc) + } + ), + RepoFile( + path='model\_index.json', + size=582, + blob\_id='d3d7c1e8c3e78eeb1640b8e2041ee256e24c9ee1', + lfs=None, + last\_commit={ + 'oid': 'b195ed2d503f3eb29637050a886d77bd81d35f0e', + 'title': 'Fix deprecation warning by changing \`CLIPFeatureExtractor\` to \`CLIPImageProcessor\`. (#54)', + 'date': datetime.datetime(2023, 5, 15, 21, 41, 59, tzinfo=datetime.timezone.utc) + }, + security={ + 'safe': True, + 'av\_scan': {'virusFound': False, 'virusNames': None}, + 'pickle\_import\_scan': None + } + ) + ... +\] + +#### list\_spaces + +[](#huggingface_hub.HfApi.list_spaces)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L2242) + +( filter: Union\[str, Iterable\[str\], None\] = Noneauthor: Optional\[str\] = Nonesearch: Optional\[str\] = Nonedatasets: Union\[str, Iterable\[str\], None\] = Nonemodels: Union\[str, Iterable\[str\], None\] = Nonelinked: bool = Falsesort: Union\[Literal\['last\_modified'\], str, None\] = Nonedirection: Optional\[Literal\[-1\]\] = Nonelimit: Optional\[int\] = Noneexpand: Optional\[List\[ExpandSpaceProperty\_T\]\] = Nonefull: Optional\[bool\] = Nonetoken: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';`Iterable[SpaceInfo]` + +Expand 12 parameters + +Parameters + +* [](#huggingface_hub.HfApi.list_spaces.filter)**filter** (`str` or `Iterable`, _optional_) — A string tag or list of tags that can be used to identify Spaces on the Hub. +* [](#huggingface_hub.HfApi.list_spaces.author)**author** (`str`, _optional_) — A string which identify the author of the returned Spaces. +* [](#huggingface_hub.HfApi.list_spaces.search)**search** (`str`, _optional_) — A string that will be contained in the returned Spaces. +* [](#huggingface_hub.HfApi.list_spaces.datasets)**datasets** (`str` or `Iterable`, _optional_) — Whether to return Spaces that make use of a dataset. The name of a specific dataset can be passed as a string. +* [](#huggingface_hub.HfApi.list_spaces.models)**models** (`str` or `Iterable`, _optional_) — Whether to return Spaces that make use of a model. The name of a specific model can be passed as a string. +* [](#huggingface_hub.HfApi.list_spaces.linked)**linked** (`bool`, _optional_) — Whether to return Spaces that make use of either a model or a dataset. +* [](#huggingface_hub.HfApi.list_spaces.sort)**sort** (`Literal["last_modified"]` or `str`, _optional_) — The key with which to sort the resulting models. Possible values are “last\_modified”, “trending\_score”, “created\_at” and “likes”. +* [](#huggingface_hub.HfApi.list_spaces.direction)**direction** (`Literal[-1]` or `int`, _optional_) — Direction in which to sort. The value `-1` sorts by descending order while all other values sort by ascending order. +* [](#huggingface_hub.HfApi.list_spaces.limit)**limit** (`int`, _optional_) — The limit on the number of Spaces fetched. Leaving this option to `None` fetches all Spaces. +* [](#huggingface_hub.HfApi.list_spaces.expand)**expand** (`List[ExpandSpaceProperty_T]`, _optional_) — List properties to return in the response. When used, only the properties in the list will be returned. This parameter cannot be used if `full` is passed. Possible values are `"author"`, `"cardData"`, `"datasets"`, `"disabled"`, `"lastModified"`, `"createdAt"`, `"likes"`, `"models"`, `"private"`, `"runtime"`, `"sdk"`, `"siblings"`, `"sha"`, `"subdomain"`, `"tags"`, `"trendingScore"`, `"usedStorage"`, `"resourceGroup"` and `"xetEnabled"`. +* [](#huggingface_hub.HfApi.list_spaces.full)**full** (`bool`, _optional_) — Whether to fetch all Spaces data, including the `last_modified`, `siblings` and `card_data` fields. +* [](#huggingface_hub.HfApi.list_spaces.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +`Iterable[SpaceInfo]` + +export const metadata = 'undefined'; + +an iterable of [huggingface\_hub.hf\_api.SpaceInfo](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.SpaceInfo) objects. + +List spaces hosted on the Huggingface Hub, given some filters. + +#### list\_user\_followers + +[](#huggingface_hub.HfApi.list_user_followers)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L9625) + +( username: strtoken: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';`Iterable[User]` + +Parameters + +* [](#huggingface_hub.HfApi.list_user_followers.username)**username** (`str`) — Username of the user to get the followers of. +* [](#huggingface_hub.HfApi.list_user_followers.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +`Iterable[User]` + +export const metadata = 'undefined'; + +A list of [User](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.User) objects with the followers of the user. + +Raises + +export const metadata = 'undefined'; + +`HTTPError` + +export const metadata = 'undefined'; + +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) — HTTP 404 If the user does not exist on the Hub. + +Get the list of followers of a user on the Hub. + +#### list\_user\_following + +[](#huggingface_hub.HfApi.list_user_following)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L9653) + +( username: strtoken: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';`Iterable[User]` + +Parameters + +* [](#huggingface_hub.HfApi.list_user_following.username)**username** (`str`) — Username of the user to get the users followed by. +* [](#huggingface_hub.HfApi.list_user_following.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +`Iterable[User]` + +export const metadata = 'undefined'; + +A list of [User](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.User) objects with the users followed by the user. + +Raises + +export const metadata = 'undefined'; + +`HTTPError` + +export const metadata = 'undefined'; + +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) — HTTP 404 If the user does not exist on the Hub. + +Get the list of users followed by a user on the Hub. + +#### list\_webhooks + +[](#huggingface_hub.HfApi.list_webhooks)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L9068) + +( token: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';`List[WebhookInfo]` + +Parameters + +* [](#huggingface_hub.HfApi.list_webhooks.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +`List[WebhookInfo]` + +export const metadata = 'undefined'; + +List of webhook info objects. + +List all configured webhooks. + +[](#huggingface_hub.HfApi.list_webhooks.example) + +Example: + +Copied + +\>>> from huggingface\_hub import list\_webhooks +\>>> webhooks = list\_webhooks() +\>>> len(webhooks) +2 +\>>> webhooks\[0\] +WebhookInfo( + id\="654bbbc16f2ec14d77f109cc", + watched=\[WebhookWatchedItem(type\="user", name="julien-c"), WebhookWatchedItem(type\="org", name="HuggingFaceH4")\], + url="https://webhook.site/a2176e82-5720-43ee-9e06-f91cb4c91548", + secret="my-secret", + domains=\["repo", "discussion"\], + disabled=False, +) + +#### merge\_pull\_request + +[](#huggingface_hub.HfApi.merge_pull_request)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L6752) + +( repo\_id: strdiscussion\_num: inttoken: Union\[bool, str, None\] = Nonecomment: Optional\[str\] = Nonerepo\_type: Optional\[str\] = None ) → export const metadata = 'undefined';[DiscussionStatusChange](/docs/huggingface_hub/v0.30.2/en/package_reference/community#huggingface_hub.DiscussionStatusChange) + +Parameters + +* [](#huggingface_hub.HfApi.merge_pull_request.repo_id)**repo\_id** (`str`) — A namespace (user or an organization) and a repo name separated by a `/`. +* [](#huggingface_hub.HfApi.merge_pull_request.discussion_num)**discussion\_num** (`int`) — The number of the Discussion or Pull Request . Must be a strictly positive integer. +* [](#huggingface_hub.HfApi.merge_pull_request.comment)**comment** (`str`, _optional_) — An optional comment to post with the status change. +* [](#huggingface_hub.HfApi.merge_pull_request.repo_type)**repo\_type** (`str`, _optional_) — Set to `"dataset"` or `"space"` if uploading to a dataset or space, `None` or `"model"` if uploading to a model. Default is `None`. +* [](#huggingface_hub.HfApi.merge_pull_request.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +[DiscussionStatusChange](/docs/huggingface_hub/v0.30.2/en/package_reference/community#huggingface_hub.DiscussionStatusChange) + +export const metadata = 'undefined'; + +the status change event + +Merges a Pull Request. + +Raises the following errors: + +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) if the HuggingFace API returned an error +* [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError) if some parameter value is invalid +* [RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) If the repository to download from cannot be found. This may be because it doesn’t exist, or because it is set to `private` and you do not have access. + +#### model\_info + +[](#huggingface_hub.HfApi.model_info)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L2518) + +( repo\_id: strrevision: Optional\[str\] = Nonetimeout: Optional\[float\] = NonesecurityStatus: Optional\[bool\] = Nonefiles\_metadata: bool = Falseexpand: Optional\[List\[ExpandModelProperty\_T\]\] = Nonetoken: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';[huggingface\_hub.hf\_api.ModelInfo](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.ModelInfo) + +Expand 7 parameters + +Parameters + +* [](#huggingface_hub.HfApi.model_info.repo_id)**repo\_id** (`str`) — A namespace (user or an organization) and a repo name separated by a `/`. +* [](#huggingface_hub.HfApi.model_info.revision)**revision** (`str`, _optional_) — The revision of the model repository from which to get the information. +* [](#huggingface_hub.HfApi.model_info.timeout)**timeout** (`float`, _optional_) — Whether to set a timeout for the request to the Hub. +* [](#huggingface_hub.HfApi.model_info.securityStatus)**securityStatus** (`bool`, _optional_) — Whether to retrieve the security status from the model repository as well. The security status will be returned in the `security_repo_status` field. +* [](#huggingface_hub.HfApi.model_info.files_metadata)**files\_metadata** (`bool`, _optional_) — Whether or not to retrieve metadata for files in the repository (size, LFS metadata, etc). Defaults to `False`. +* [](#huggingface_hub.HfApi.model_info.expand)**expand** (`List[ExpandModelProperty_T]`, _optional_) — List properties to return in the response. When used, only the properties in the list will be returned. This parameter cannot be used if `securityStatus` or `files_metadata` are passed. Possible values are `"author"`, `"baseModels"`, `"cardData"`, `"childrenModelCount"`, `"config"`, `"createdAt"`, `"disabled"`, `"downloads"`, `"downloadsAllTime"`, `"gated"`, `"gguf"`, `"inference"`, `"inferenceProviderMapping"`, `"lastModified"`, `"library_name"`, `"likes"`, `"mask_token"`, `"model-index"`, `"pipeline_tag"`, `"private"`, `"safetensors"`, `"sha"`, `"siblings"`, `"spaces"`, `"tags"`, `"transformersInfo"`, `"trendingScore"`, `"widgetData"`, `"usedStorage"`, `"resourceGroup"` and `"xetEnabled"`. +* [](#huggingface_hub.HfApi.model_info.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +[huggingface\_hub.hf\_api.ModelInfo](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.ModelInfo) + +export const metadata = 'undefined'; + +The model repository information. + +Get info on one specific model on huggingface.co + +Model can be private if you pass an acceptable token or are logged in. + +Raises the following errors: + +* [RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) If the repository to download from cannot be found. This may be because it doesn’t exist, or because it is set to `private` and you do not have access. +* [RevisionNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RevisionNotFoundError) If the revision to download from cannot be found. + +#### move\_repo + +[](#huggingface_hub.HfApi.move_repo)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L3915) + +( from\_id: strto\_id: strrepo\_type: Optional\[str\] = Nonetoken: Union\[str, bool, None\] = None ) + +Parameters + +* [](#huggingface_hub.HfApi.move_repo.from_id)**from\_id** (`str`) — A namespace (user or an organization) and a repo name separated by a `/`. Original repository identifier. +* [](#huggingface_hub.HfApi.move_repo.to_id)**to\_id** (`str`) — A namespace (user or an organization) and a repo name separated by a `/`. Final repository identifier. +* [](#huggingface_hub.HfApi.move_repo.repo_type)**repo\_type** (`str`, _optional_) — Set to `"dataset"` or `"space"` if uploading to a dataset or space, `None` or `"model"` if uploading to a model. Default is `None`. +* [](#huggingface_hub.HfApi.move_repo.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Moving a repository from namespace1/repo\_name1 to namespace2/repo\_name2 + +Note there are certain limitations. For more information about moving repositories, please see [https://hf.co/docs/hub/repositories-settings#renaming-or-transferring-a-repo](https://hf.co/docs/hub/repositories-settings#renaming-or-transferring-a-repo). + +Raises the following errors: + +* [RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) If the repository to download from cannot be found. This may be because it doesn’t exist, or because it is set to `private` and you do not have access. + +#### paper\_info + +[](#huggingface_hub.HfApi.paper_info)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L9727) + +( id: str ) → export const metadata = 'undefined';`PaperInfo` + +Parameters + +* [](#huggingface_hub.HfApi.paper_info.id)**id** (`str`, **optional**) — ArXiv id of the paper. + +Returns + +export const metadata = 'undefined'; + +`PaperInfo` + +export const metadata = 'undefined'; + +A `PaperInfo` object. + +Raises + +export const metadata = 'undefined'; + +`HTTPError` + +export const metadata = 'undefined'; + +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) — HTTP 404 If the paper does not exist on the Hub. + +Get information for a paper on the Hub. + +#### parse\_safetensors\_file\_metadata + +[](#huggingface_hub.HfApi.parse_safetensors_file_metadata)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L5763) + +( repo\_id: strfilename: strrepo\_type: Optional\[str\] = Nonerevision: Optional\[str\] = Nonetoken: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';`SafetensorsFileMetadata` + +Expand 5 parameters + +Parameters + +* [](#huggingface_hub.HfApi.parse_safetensors_file_metadata.repo_id)**repo\_id** (`str`) — A user or an organization name and a repo name separated by a `/`. +* [](#huggingface_hub.HfApi.parse_safetensors_file_metadata.filename)**filename** (`str`) — The name of the file in the repo. +* [](#huggingface_hub.HfApi.parse_safetensors_file_metadata.repo_type)**repo\_type** (`str`, _optional_) — Set to `"dataset"` or `"space"` if the file is in a dataset or space, `None` or `"model"` if in a model. Default is `None`. +* [](#huggingface_hub.HfApi.parse_safetensors_file_metadata.revision)**revision** (`str`, _optional_) — The git revision to fetch the file from. Can be a branch name, a tag, or a commit hash. Defaults to the head of the `"main"` branch. +* [](#huggingface_hub.HfApi.parse_safetensors_file_metadata.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +`SafetensorsFileMetadata` + +export const metadata = 'undefined'; + +information related to a safetensors file. + +Raises + +export const metadata = 'undefined'; + +`NotASafetensorsRepoError` or `SafetensorsParsingError` + +export const metadata = 'undefined'; + +* `NotASafetensorsRepoError` — If the repo is not a safetensors repo i.e. doesn’t have either a `model.safetensors` or a `model.safetensors.index.json` file. +* `SafetensorsParsingError` — If a safetensors file header couldn’t be parsed correctly. + +Parse metadata from a safetensors file on the Hub. + +To parse metadata from all safetensors files in a repo at once, use [get\_safetensors\_metadata()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.get_safetensors_metadata). + +For more details regarding the safetensors format, check out [https://huggingface.co/docs/safetensors/index#format](https://huggingface.co/docs/safetensors/index#format). + +#### pause\_inference\_endpoint + +[](#huggingface_hub.HfApi.pause_inference_endpoint)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L7986) + +( name: strnamespace: Optional\[str\] = Nonetoken: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';[InferenceEndpoint](/docs/huggingface_hub/v0.30.2/en/package_reference/inference_endpoints#huggingface_hub.InferenceEndpoint) + +Parameters + +* [](#huggingface_hub.HfApi.pause_inference_endpoint.name)**name** (`str`) — The name of the Inference Endpoint to pause. +* [](#huggingface_hub.HfApi.pause_inference_endpoint.namespace)**namespace** (`str`, _optional_) — The namespace in which the Inference Endpoint is located. Defaults to the current user. +* [](#huggingface_hub.HfApi.pause_inference_endpoint.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +[InferenceEndpoint](/docs/huggingface_hub/v0.30.2/en/package_reference/inference_endpoints#huggingface_hub.InferenceEndpoint) + +export const metadata = 'undefined'; + +information about the paused Inference Endpoint. + +Pause an Inference Endpoint. + +A paused Inference Endpoint will not be charged. It can be resumed at any time using [resume\_inference\_endpoint()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.resume_inference_endpoint). This is different than scaling the Inference Endpoint to zero with [scale\_to\_zero\_inference\_endpoint()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.scale_to_zero_inference_endpoint), which would be automatically restarted when a request is made to it. + +For convenience, you can also pause an Inference Endpoint using [pause\_inference\_endpoint()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.pause_inference_endpoint). + +#### pause\_space + +[](#huggingface_hub.HfApi.pause_space)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L7214) + +( repo\_id: strtoken: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';[SpaceRuntime](/docs/huggingface_hub/v0.30.2/en/package_reference/space_runtime#huggingface_hub.SpaceRuntime) + +Expand 2 parameters + +Parameters + +* [](#huggingface_hub.HfApi.pause_space.repo_id)**repo\_id** (`str`) — ID of the Space to pause. Example: `"Salesforce/BLIP2"`. +* [](#huggingface_hub.HfApi.pause_space.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +[SpaceRuntime](/docs/huggingface_hub/v0.30.2/en/package_reference/space_runtime#huggingface_hub.SpaceRuntime) + +export const metadata = 'undefined'; + +Runtime information about your Space including `stage=PAUSED` and requested hardware. + +Raises + +export const metadata = 'undefined'; + +[RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) or [HfHubHTTPError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.HfHubHTTPError) or [BadRequestError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.BadRequestError) + +export const metadata = 'undefined'; + +* [RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) — If your Space is not found (error 404). Most probably wrong repo\_id or your space is private but you are not authenticated. +* [HfHubHTTPError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.HfHubHTTPError) — 403 Forbidden: only the owner of a Space can pause it. If you want to manage a Space that you don’t own, either ask the owner by opening a Discussion or duplicate the Space. +* [BadRequestError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.BadRequestError) — If your Space is a static Space. Static Spaces are always running and never billed. If you want to hide a static Space, you can set it to private. + +Pause your Space. + +A paused Space stops executing until manually restarted by its owner. This is different from the sleeping state in which free Spaces go after 48h of inactivity. Paused time is not billed to your account, no matter the hardware you’ve selected. To restart your Space, use [restart\_space()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.restart_space) and go to your Space settings page. + +For more details, please visit [the docs](https://huggingface.co/docs/hub/spaces-gpus#pause). + +#### permanently\_delete\_lfs\_files + +[](#huggingface_hub.HfApi.permanently_delete_lfs_files)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L3509) + +( repo\_id: strlfs\_files: Iterable\[LFSFileInfo\]rewrite\_history: bool = Truerepo\_type: Optional\[str\] = Nonetoken: Union\[bool, str, None\] = None ) + +Parameters + +* [](#huggingface_hub.HfApi.permanently_delete_lfs_files.repo_id)**repo\_id** (`str`) — The repository for which you are listing LFS files. +* [](#huggingface_hub.HfApi.permanently_delete_lfs_files.lfs_files)**lfs\_files** (`Iterable[LFSFileInfo]`) — An iterable of `LFSFileInfo` items to permanently delete from the repo. Use [list\_lfs\_files()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.list_lfs_files) to list all LFS files from a repo. +* [](#huggingface_hub.HfApi.permanently_delete_lfs_files.rewrite_history)**rewrite\_history** (`bool`, _optional_, default to `True`) — Whether to rewrite repository history to remove file pointers referencing the deleted LFS files (recommended). +* [](#huggingface_hub.HfApi.permanently_delete_lfs_files.repo_type)**repo\_type** (`str`, _optional_) — Type of repository. Set to `"dataset"` or `"space"` if listing from a dataset or space, `None` or `"model"` if listing from a model. Default is `None`. +* [](#huggingface_hub.HfApi.permanently_delete_lfs_files.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Permanently delete LFS files from a repo on the Hub. + +This is a permanent action that will affect all commits referencing the deleted files and might corrupt your repository. This is a non-revertible operation. Use it only if you know what you are doing. + +[](#huggingface_hub.HfApi.permanently_delete_lfs_files.example) + +Example: + +Copied + +\>>> from huggingface\_hub import HfApi +\>>> api = HfApi() +\>>> lfs\_files = api.list\_lfs\_files("username/my-cool-repo") + +\# Filter files files to delete based on a combination of \`filename\`, \`pushed\_at\`, \`ref\` or \`size\`. +\# e.g. select only LFS files in the "checkpoints" folder +\>>> lfs\_files\_to\_delete = (lfs\_file for lfs\_file in lfs\_files if lfs\_file.filename.startswith("checkpoints/")) + +\# Permanently delete LFS files +\>>> api.permanently\_delete\_lfs\_files("username/my-cool-repo", lfs\_files\_to\_delete) + +#### preupload\_lfs\_files + +[](#huggingface_hub.HfApi.preupload_lfs_files)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L4310) + +( repo\_id: stradditions: Iterable\[CommitOperationAdd\]token: Union\[str, bool, None\] = Nonerepo\_type: Optional\[str\] = Nonerevision: Optional\[str\] = Nonecreate\_pr: Optional\[bool\] = Nonenum\_threads: int = 5free\_memory: bool = Truegitignore\_content: Optional\[str\] = None ) + +Expand 8 parameters + +Parameters + +* [](#huggingface_hub.HfApi.preupload_lfs_files.repo_id)**repo\_id** (`str`) — The repository in which you will commit the files, for example: `"username/custom_transformers"`. +* [](#huggingface_hub.HfApi.preupload_lfs_files.operations)**operations** (`Iterable` of [CommitOperationAdd](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.CommitOperationAdd)) — The list of files to upload. Warning: the objects in this list will be mutated to include information relative to the upload. Do not reuse the same objects for multiple commits. +* [](#huggingface_hub.HfApi.preupload_lfs_files.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. +* [](#huggingface_hub.HfApi.preupload_lfs_files.repo_type)**repo\_type** (`str`, _optional_) — The type of repository to upload to (e.g. `"model"` -default-, `"dataset"` or `"space"`). +* [](#huggingface_hub.HfApi.preupload_lfs_files.revision)**revision** (`str`, _optional_) — The git revision to commit from. Defaults to the head of the `"main"` branch. +* [](#huggingface_hub.HfApi.preupload_lfs_files.create_pr)**create\_pr** (`boolean`, _optional_) — Whether or not you plan to create a Pull Request with that commit. Defaults to `False`. +* [](#huggingface_hub.HfApi.preupload_lfs_files.num_threads)**num\_threads** (`int`, _optional_) — Number of concurrent threads for uploading files. Defaults to 5. Setting it to 2 means at most 2 files will be uploaded concurrently. +* [](#huggingface_hub.HfApi.preupload_lfs_files.gitignore_content)**gitignore\_content** (`str`, _optional_) — The content of the `.gitignore` file to know which files should be ignored. The order of priority is to first check if `gitignore_content` is passed, then check if the `.gitignore` file is present in the list of files to commit and finally default to the `.gitignore` file already hosted on the Hub (if any). + +Pre-upload LFS files to S3 in preparation on a future commit. + +This method is useful if you are generating the files to upload on-the-fly and you don’t want to store them in memory before uploading them all at once. + +This is a power-user method. You shouldn’t need to call it directly to make a normal commit. Use [create\_commit()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.create_commit) directly instead. + +Commit operations will be mutated during the process. In particular, the attached `path_or_fileobj` will be removed after the upload to save memory (and replaced by an empty `bytes` object). Do not reuse the same objects except to pass them to [create\_commit()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.create_commit). If you don’t want to remove the attached content from the commit operation object, pass `free_memory=False`. + +[](#huggingface_hub.HfApi.preupload_lfs_files.example) + +Example: + +Copied + +\>>> from huggingface\_hub import CommitOperationAdd, preupload\_lfs\_files, create\_commit, create\_repo + +\>>> repo\_id = create\_repo("test\_preupload").repo\_id + +\# Generate and preupload LFS files one by one +\>>> operations = \[\] \# List of all \`CommitOperationAdd\` objects that will be generated +\>>> for i in range(5): +... content = ... \# generate binary content +... addition = CommitOperationAdd(path\_in\_repo=f"shard\_{i}\_of\_5.bin", path\_or\_fileobj=content) +... preupload\_lfs\_files(repo\_id, additions=\[addition\]) \# upload + free memory +... operations.append(addition) + +\# Create commit +\>>> create\_commit(repo\_id, operations=operations, commit\_message="Commit all shards") + +#### reject\_access\_request + +[](#huggingface_hub.HfApi.reject_access_request)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L8881) + +( repo\_id: struser: strrepo\_type: Optional\[str\] = Nonerejection\_reason: Optional\[str\]token: Union\[bool, str, None\] = None ) + +Expand 5 parameters + +Parameters + +* [](#huggingface_hub.HfApi.reject_access_request.repo_id)**repo\_id** (`str`) — The id of the repo to reject access request for. +* [](#huggingface_hub.HfApi.reject_access_request.user)**user** (`str`) — The username of the user which access request should be rejected. +* [](#huggingface_hub.HfApi.reject_access_request.repo_type)**repo\_type** (`str`, _optional_) — The type of the repo to reject access request for. Must be one of `model`, `dataset` or `space`. Defaults to `model`. +* [](#huggingface_hub.HfApi.reject_access_request.rejection_reason)**rejection\_reason** (`str`, _optional_) — Optional rejection reason that will be visible to the user (max 200 characters). +* [](#huggingface_hub.HfApi.reject_access_request.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Raises + +export const metadata = 'undefined'; + +`HTTPError` + +export const metadata = 'undefined'; + +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) — HTTP 400 if the repo is not gated. +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) — HTTP 403 if you only have read-only access to the repo. This can be the case if you don’t have `write` or `admin` role in the organization the repo belongs to or if you passed a `read` token. +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) — HTTP 404 if the user does not exist on the Hub. +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) — HTTP 404 if the user access request cannot be found. +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) — HTTP 404 if the user access request is already in the rejected list. + +Reject an access request from a user for a given gated repo. + +A rejected request will go to the rejected list. The user cannot download any file of the repo. Rejected requests can be accepted or cancelled at any time using [accept\_access\_request()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.accept_access_request) and [cancel\_access\_request()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.cancel_access_request). A cancelled request will go back to the pending list while an accepted request will go to the accepted list. + +For more info about gated repos, see [https://huggingface.co/docs/hub/models-gated](https://huggingface.co/docs/hub/models-gated). + +#### rename\_discussion + +[](#huggingface_hub.HfApi.rename_discussion)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L6604) + +( repo\_id: strdiscussion\_num: intnew\_title: strtoken: Union\[bool, str, None\] = Nonerepo\_type: Optional\[str\] = None ) → export const metadata = 'undefined';[DiscussionTitleChange](/docs/huggingface_hub/v0.30.2/en/package_reference/community#huggingface_hub.DiscussionTitleChange) + +Parameters + +* [](#huggingface_hub.HfApi.rename_discussion.repo_id)**repo\_id** (`str`) — A namespace (user or an organization) and a repo name separated by a `/`. +* [](#huggingface_hub.HfApi.rename_discussion.discussion_num)**discussion\_num** (`int`) — The number of the Discussion or Pull Request . Must be a strictly positive integer. +* [](#huggingface_hub.HfApi.rename_discussion.new_title)**new\_title** (`str`) — The new title for the discussion +* [](#huggingface_hub.HfApi.rename_discussion.repo_type)**repo\_type** (`str`, _optional_) — Set to `"dataset"` or `"space"` if uploading to a dataset or space, `None` or `"model"` if uploading to a model. Default is `None`. +* [](#huggingface_hub.HfApi.rename_discussion.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +[DiscussionTitleChange](/docs/huggingface_hub/v0.30.2/en/package_reference/community#huggingface_hub.DiscussionTitleChange) + +export const metadata = 'undefined'; + +the title change event + +Renames a Discussion. + +[](#huggingface_hub.HfApi.rename_discussion.example) + +Examples: + +Copied + +\>>> new\_title = "New title, fixing a typo" +\>>> HfApi().rename\_discussion( +... repo\_id="username/repo\_name", +... discussion\_num=34 +... new\_title=new\_title +... ) +\# DiscussionTitleChange(id='deadbeef0000000', type='title-change', ...) + +Raises the following errors: + +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) if the HuggingFace API returned an error +* [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError) if some parameter value is invalid +* [RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) If the repository to download from cannot be found. This may be because it doesn’t exist, or because it is set to `private` and you do not have access. + +#### repo\_exists + +[](#huggingface_hub.HfApi.repo_exists)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L2816) + +( repo\_id: strrepo\_type: Optional\[str\] = Nonetoken: Union\[str, bool, None\] = None ) + +Parameters + +* [](#huggingface_hub.HfApi.repo_exists.repo_id)**repo\_id** (`str`) — A namespace (user or an organization) and a repo name separated by a `/`. +* [](#huggingface_hub.HfApi.repo_exists.repo_type)**repo\_type** (`str`, _optional_) — Set to `"dataset"` or `"space"` if getting repository info from a dataset or a space, `None` or `"model"` if getting repository info from a model. Default is `None`. +* [](#huggingface_hub.HfApi.repo_exists.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Checks if a repository exists on the Hugging Face Hub. + +[](#huggingface_hub.HfApi.repo_exists.example) + +Examples: + +Copied + +\>>> from huggingface\_hub import repo\_exists +\>>> repo\_exists("google/gemma-7b") +True +\>>> repo\_exists("google/not-a-repo") +False + +#### repo\_info + +[](#huggingface_hub.HfApi.repo_info)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L2742) + +( repo\_id: strrevision: Optional\[str\] = Nonerepo\_type: Optional\[str\] = Nonetimeout: Optional\[float\] = Nonefiles\_metadata: bool = Falseexpand: Optional\[Union\[ExpandModelProperty\_T, ExpandDatasetProperty\_T, ExpandSpaceProperty\_T\]\] = Nonetoken: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';`Union[SpaceInfo, DatasetInfo, ModelInfo]` + +Expand 7 parameters + +Parameters + +* [](#huggingface_hub.HfApi.repo_info.repo_id)**repo\_id** (`str`) — A namespace (user or an organization) and a repo name separated by a `/`. +* [](#huggingface_hub.HfApi.repo_info.revision)**revision** (`str`, _optional_) — The revision of the repository from which to get the information. +* [](#huggingface_hub.HfApi.repo_info.repo_type)**repo\_type** (`str`, _optional_) — Set to `"dataset"` or `"space"` if getting repository info from a dataset or a space, `None` or `"model"` if getting repository info from a model. Default is `None`. +* [](#huggingface_hub.HfApi.repo_info.timeout)**timeout** (`float`, _optional_) — Whether to set a timeout for the request to the Hub. +* [](#huggingface_hub.HfApi.repo_info.expand)**expand** (`ExpandModelProperty_T` or `ExpandDatasetProperty_T` or `ExpandSpaceProperty_T`, _optional_) — List properties to return in the response. When used, only the properties in the list will be returned. This parameter cannot be used if `files_metadata` is passed. For an exhaustive list of available properties, check out [model\_info()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.model_info), [dataset\_info()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.dataset_info) or [space\_info()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.space_info). +* [](#huggingface_hub.HfApi.repo_info.files_metadata)**files\_metadata** (`bool`, _optional_) — Whether or not to retrieve metadata for files in the repository (size, LFS metadata, etc). Defaults to `False`. +* [](#huggingface_hub.HfApi.repo_info.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +`Union[SpaceInfo, DatasetInfo, ModelInfo]` + +export const metadata = 'undefined'; + +The repository information, as a [huggingface\_hub.hf\_api.DatasetInfo](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.DatasetInfo), [huggingface\_hub.hf\_api.ModelInfo](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.ModelInfo) or [huggingface\_hub.hf\_api.SpaceInfo](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.SpaceInfo) object. + +Get the info object for a given repo of a given type. + +Raises the following errors: + +* [RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) If the repository to download from cannot be found. This may be because it doesn’t exist, or because it is set to `private` and you do not have access. +* [RevisionNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RevisionNotFoundError) If the revision to download from cannot be found. + +#### request\_space\_hardware + +[](#huggingface_hub.HfApi.request_space_hardware)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L7110) + +( repo\_id: strhardware: SpaceHardwaretoken: Union\[bool, str, None\] = Nonesleep\_time: Optional\[int\] = None ) → export const metadata = 'undefined';[SpaceRuntime](/docs/huggingface_hub/v0.30.2/en/package_reference/space_runtime#huggingface_hub.SpaceRuntime) + +Parameters + +* [](#huggingface_hub.HfApi.request_space_hardware.repo_id)**repo\_id** (`str`) — ID of the repo to update. Example: `"bigcode/in-the-stack"`. +* [](#huggingface_hub.HfApi.request_space_hardware.hardware)**hardware** (`str` or [SpaceHardware](/docs/huggingface_hub/v0.30.2/en/package_reference/space_runtime#huggingface_hub.SpaceHardware)) — Hardware on which to run the Space. Example: `"t4-medium"`. +* [](#huggingface_hub.HfApi.request_space_hardware.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. +* [](#huggingface_hub.HfApi.request_space_hardware.sleep_time)**sleep\_time** (`int`, _optional_) — Number of seconds of inactivity to wait before a Space is put to sleep. Set to `-1` if you don’t want your Space to sleep (default behavior for upgraded hardware). For free hardware, you can’t configure the sleep time (value is fixed to 48 hours of inactivity). See [https://huggingface.co/docs/hub/spaces-gpus#sleep-time](https://huggingface.co/docs/hub/spaces-gpus#sleep-time) for more details. + +Returns + +export const metadata = 'undefined'; + +[SpaceRuntime](/docs/huggingface_hub/v0.30.2/en/package_reference/space_runtime#huggingface_hub.SpaceRuntime) + +export const metadata = 'undefined'; + +Runtime information about a Space including Space stage and hardware. + +Request new hardware for a Space. + +It is also possible to request hardware directly when creating the Space repo! See [create\_repo()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.create_repo) for details. + +#### request\_space\_storage + +[](#huggingface_hub.HfApi.request_space_storage)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L7417) + +( repo\_id: strstorage: SpaceStoragetoken: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';[SpaceRuntime](/docs/huggingface_hub/v0.30.2/en/package_reference/space_runtime#huggingface_hub.SpaceRuntime) + +Parameters + +* [](#huggingface_hub.HfApi.request_space_storage.repo_id)**repo\_id** (`str`) — ID of the Space to update. Example: `"open-llm-leaderboard/open_llm_leaderboard"`. +* [](#huggingface_hub.HfApi.request_space_storage.storage)**storage** (`str` or [SpaceStorage](/docs/huggingface_hub/v0.30.2/en/package_reference/space_runtime#huggingface_hub.SpaceStorage)) — Storage tier. Either ‘small’, ‘medium’, or ‘large’. +* [](#huggingface_hub.HfApi.request_space_storage.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +[SpaceRuntime](/docs/huggingface_hub/v0.30.2/en/package_reference/space_runtime#huggingface_hub.SpaceRuntime) + +export const metadata = 'undefined'; + +Runtime information about a Space including Space stage and hardware. + +Request persistent storage for a Space. + +It is not possible to decrease persistent storage after its granted. To do so, you must delete it via [delete\_space\_storage()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.delete_space_storage). + +#### restart\_space + +[](#huggingface_hub.HfApi.restart_space)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L7253) + +( repo\_id: strtoken: Union\[bool, str, None\] = Nonefactory\_reboot: bool = False ) → export const metadata = 'undefined';[SpaceRuntime](/docs/huggingface_hub/v0.30.2/en/package_reference/space_runtime#huggingface_hub.SpaceRuntime) + +Expand 3 parameters + +Parameters + +* [](#huggingface_hub.HfApi.restart_space.repo_id)**repo\_id** (`str`) — ID of the Space to restart. Example: `"Salesforce/BLIP2"`. +* [](#huggingface_hub.HfApi.restart_space.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. +* [](#huggingface_hub.HfApi.restart_space.factory_reboot)**factory\_reboot** (`bool`, _optional_) — If `True`, the Space will be rebuilt from scratch without caching any requirements. + +Returns + +export const metadata = 'undefined'; + +[SpaceRuntime](/docs/huggingface_hub/v0.30.2/en/package_reference/space_runtime#huggingface_hub.SpaceRuntime) + +export const metadata = 'undefined'; + +Runtime information about your Space. + +Raises + +export const metadata = 'undefined'; + +[RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) or [HfHubHTTPError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.HfHubHTTPError) or [BadRequestError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.BadRequestError) + +export const metadata = 'undefined'; + +* [RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) — If your Space is not found (error 404). Most probably wrong repo\_id or your space is private but you are not authenticated. +* [HfHubHTTPError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.HfHubHTTPError) — 403 Forbidden: only the owner of a Space can restart it. If you want to restart a Space that you don’t own, either ask the owner by opening a Discussion or duplicate the Space. +* [BadRequestError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.BadRequestError) — If your Space is a static Space. Static Spaces are always running and never billed. If you want to hide a static Space, you can set it to private. + +Restart your Space. + +This is the only way to programmatically restart a Space if you’ve put it on Pause (see [pause\_space()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.pause_space)). You must be the owner of the Space to restart it. If you are using an upgraded hardware, your account will be billed as soon as the Space is restarted. You can trigger a restart no matter the current state of a Space. + +For more details, please visit [the docs](https://huggingface.co/docs/hub/spaces-gpus#pause). + +#### resume\_inference\_endpoint + +[](#huggingface_hub.HfApi.resume_inference_endpoint)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L8021) + +( name: strnamespace: Optional\[str\] = Nonerunning\_ok: bool = Truetoken: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';[InferenceEndpoint](/docs/huggingface_hub/v0.30.2/en/package_reference/inference_endpoints#huggingface_hub.InferenceEndpoint) + +Parameters + +* [](#huggingface_hub.HfApi.resume_inference_endpoint.name)**name** (`str`) — The name of the Inference Endpoint to resume. +* [](#huggingface_hub.HfApi.resume_inference_endpoint.namespace)**namespace** (`str`, _optional_) — The namespace in which the Inference Endpoint is located. Defaults to the current user. +* [](#huggingface_hub.HfApi.resume_inference_endpoint.running_ok)**running\_ok** (`bool`, _optional_) — If `True`, the method will not raise an error if the Inference Endpoint is already running. Defaults to `True`. +* [](#huggingface_hub.HfApi.resume_inference_endpoint.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +[InferenceEndpoint](/docs/huggingface_hub/v0.30.2/en/package_reference/inference_endpoints#huggingface_hub.InferenceEndpoint) + +export const metadata = 'undefined'; + +information about the resumed Inference Endpoint. + +Resume an Inference Endpoint. + +For convenience, you can also resume an Inference Endpoint using [InferenceEndpoint.resume()](/docs/huggingface_hub/v0.30.2/en/package_reference/inference_endpoints#huggingface_hub.InferenceEndpoint.resume). + +#### revision\_exists + +[](#huggingface_hub.HfApi.revision_exists)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L2860) + +( repo\_id: strrevision: strrepo\_type: Optional\[str\] = Nonetoken: Union\[str, bool, None\] = None ) + +Parameters + +* [](#huggingface_hub.HfApi.revision_exists.repo_id)**repo\_id** (`str`) — A namespace (user or an organization) and a repo name separated by a `/`. +* [](#huggingface_hub.HfApi.revision_exists.revision)**revision** (`str`) — The revision of the repository to check. +* [](#huggingface_hub.HfApi.revision_exists.repo_type)**repo\_type** (`str`, _optional_) — Set to `"dataset"` or `"space"` if getting repository info from a dataset or a space, `None` or `"model"` if getting repository info from a model. Default is `None`. +* [](#huggingface_hub.HfApi.revision_exists.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Checks if a specific revision exists on a repo on the Hugging Face Hub. + +[](#huggingface_hub.HfApi.revision_exists.example) + +Examples: + +Copied + +\>>> from huggingface\_hub import revision\_exists +\>>> revision\_exists("google/gemma-7b", "float16") +True +\>>> revision\_exists("google/gemma-7b", "not-a-revision") +False + +#### run\_as\_future + +[](#huggingface_hub.HfApi.run_as_future)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L1678) + +( fn: Callable\[..., R\]\*args\*\*kwargs ) → export const metadata = 'undefined';`Future` + +Parameters + +* [](#huggingface_hub.HfApi.run_as_future.fn)**fn** (`Callable`) — The method to run in the background. +* [](#huggingface_hub.HfApi.run_as_future.*args,)**\*args,** \*\*kwargs — Arguments with which the method will be called. + +Returns + +export const metadata = 'undefined'; + +`Future` + +export const metadata = 'undefined'; + +a [Future](https://docs.python.org/3/library/concurrent.futures.html#future-objects) instance to get the result of the task. + +Run a method in the background and return a Future instance. + +The main goal is to run methods without blocking the main thread (e.g. to push data during a training). Background jobs are queued to preserve order but are not ran in parallel. If you need to speed-up your scripts by parallelizing lots of call to the API, you must setup and use your own [ThreadPoolExecutor](https://docs.python.org/3/library/concurrent.futures.html#threadpoolexecutor). + +Note: Most-used methods like [upload\_file()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.upload_file), [upload\_folder()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.upload_folder) and [create\_commit()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.create_commit) have a `run_as_future: bool` argument to directly call them in the background. This is equivalent to calling `api.run_as_future(...)` on them but less verbose. + +[](#huggingface_hub.HfApi.run_as_future.example) + +Example: + +Copied + +\>>> from huggingface\_hub import HfApi +\>>> api = HfApi() +\>>> future = api.run\_as\_future(api.whoami) \# instant +\>>> future.done() +False +\>>> future.result() \# wait until complete and return result +(...) +\>>> future.done() +True + +#### scale\_to\_zero\_inference\_endpoint + +[](#huggingface_hub.HfApi.scale_to_zero_inference_endpoint)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L8067) + +( name: strnamespace: Optional\[str\] = Nonetoken: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';[InferenceEndpoint](/docs/huggingface_hub/v0.30.2/en/package_reference/inference_endpoints#huggingface_hub.InferenceEndpoint) + +Parameters + +* [](#huggingface_hub.HfApi.scale_to_zero_inference_endpoint.name)**name** (`str`) — The name of the Inference Endpoint to scale to zero. +* [](#huggingface_hub.HfApi.scale_to_zero_inference_endpoint.namespace)**namespace** (`str`, _optional_) — The namespace in which the Inference Endpoint is located. Defaults to the current user. +* [](#huggingface_hub.HfApi.scale_to_zero_inference_endpoint.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +[InferenceEndpoint](/docs/huggingface_hub/v0.30.2/en/package_reference/inference_endpoints#huggingface_hub.InferenceEndpoint) + +export const metadata = 'undefined'; + +information about the scaled-to-zero Inference Endpoint. + +Scale Inference Endpoint to zero. + +An Inference Endpoint scaled to zero will not be charged. It will be resume on the next request to it, with a cold start delay. This is different than pausing the Inference Endpoint with [pause\_inference\_endpoint()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.pause_inference_endpoint), which would require a manual resume with [resume\_inference\_endpoint()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.resume_inference_endpoint). + +For convenience, you can also scale an Inference Endpoint to zero using [InferenceEndpoint.scale\_to\_zero()](/docs/huggingface_hub/v0.30.2/en/package_reference/inference_endpoints#huggingface_hub.InferenceEndpoint.scale_to_zero). + +#### set\_space\_sleep\_time + +[](#huggingface_hub.HfApi.set_space_sleep_time)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L7163) + +( repo\_id: strsleep\_time: inttoken: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';[SpaceRuntime](/docs/huggingface_hub/v0.30.2/en/package_reference/space_runtime#huggingface_hub.SpaceRuntime) + +Parameters + +* [](#huggingface_hub.HfApi.set_space_sleep_time.repo_id)**repo\_id** (`str`) — ID of the repo to update. Example: `"bigcode/in-the-stack"`. +* [](#huggingface_hub.HfApi.set_space_sleep_time.sleep_time)**sleep\_time** (`int`, _optional_) — Number of seconds of inactivity to wait before a Space is put to sleep. Set to `-1` if you don’t want your Space to pause (default behavior for upgraded hardware). For free hardware, you can’t configure the sleep time (value is fixed to 48 hours of inactivity). See [https://huggingface.co/docs/hub/spaces-gpus#sleep-time](https://huggingface.co/docs/hub/spaces-gpus#sleep-time) for more details. +* [](#huggingface_hub.HfApi.set_space_sleep_time.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +[SpaceRuntime](/docs/huggingface_hub/v0.30.2/en/package_reference/space_runtime#huggingface_hub.SpaceRuntime) + +export const metadata = 'undefined'; + +Runtime information about a Space including Space stage and hardware. + +Set a custom sleep time for a Space running on upgraded hardware.. + +Your Space will go to sleep after X seconds of inactivity. You are not billed when your Space is in “sleep” mode. If a new visitor lands on your Space, it will “wake it up”. Only upgraded hardware can have a configurable sleep time. To know more about the sleep stage, please refer to [https://huggingface.co/docs/hub/spaces-gpus#sleep-time](https://huggingface.co/docs/hub/spaces-gpus#sleep-time). + +It is also possible to set a custom sleep time when requesting hardware with [request\_space\_hardware()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.request_space_hardware). + +#### snapshot\_download + +[](#huggingface_hub.HfApi.snapshot_download)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L5498) + +( repo\_id: strrepo\_type: Optional\[str\] = Nonerevision: Optional\[str\] = Nonecache\_dir: Union\[str, Path, None\] = Nonelocal\_dir: Union\[str, Path, None\] = Noneproxies: Optional\[Dict\] = Noneetag\_timeout: float = 10force\_download: bool = Falsetoken: Union\[bool, str, None\] = Nonelocal\_files\_only: bool = Falseallow\_patterns: Optional\[Union\[List\[str\], str\]\] = Noneignore\_patterns: Optional\[Union\[List\[str\], str\]\] = Nonemax\_workers: int = 8tqdm\_class: Optional\[base\_tqdm\] = Nonelocal\_dir\_use\_symlinks: Union\[bool, Literal\['auto'\]\] = 'auto'resume\_download: Optional\[bool\] = None ) → export const metadata = 'undefined';`str` + +Expand 14 parameters + +Parameters + +* [](#huggingface_hub.HfApi.snapshot_download.repo_id)**repo\_id** (`str`) — A user or an organization name and a repo name separated by a `/`. +* [](#huggingface_hub.HfApi.snapshot_download.repo_type)**repo\_type** (`str`, _optional_) — Set to `"dataset"` or `"space"` if downloading from a dataset or space, `None` or `"model"` if downloading from a model. Default is `None`. +* [](#huggingface_hub.HfApi.snapshot_download.revision)**revision** (`str`, _optional_) — An optional Git revision id which can be a branch name, a tag, or a commit hash. +* [](#huggingface_hub.HfApi.snapshot_download.cache_dir)**cache\_dir** (`str`, `Path`, _optional_) — Path to the folder where cached files are stored. +* [](#huggingface_hub.HfApi.snapshot_download.local_dir)**local\_dir** (`str` or `Path`, _optional_) — If provided, the downloaded files will be placed under this directory. +* [](#huggingface_hub.HfApi.snapshot_download.proxies)**proxies** (`dict`, _optional_) — Dictionary mapping protocol to the URL of the proxy passed to `requests.request`. +* [](#huggingface_hub.HfApi.snapshot_download.etag_timeout)**etag\_timeout** (`float`, _optional_, defaults to `10`) — When fetching ETag, how many seconds to wait for the server to send data before giving up which is passed to `requests.request`. +* [](#huggingface_hub.HfApi.snapshot_download.force_download)**force\_download** (`bool`, _optional_, defaults to `False`) — Whether the file should be downloaded even if it already exists in the local cache. +* [](#huggingface_hub.HfApi.snapshot_download.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. +* [](#huggingface_hub.HfApi.snapshot_download.local_files_only)**local\_files\_only** (`bool`, _optional_, defaults to `False`) — If `True`, avoid downloading the file and return the path to the local cached file if it exists. +* [](#huggingface_hub.HfApi.snapshot_download.allow_patterns)**allow\_patterns** (`List[str]` or `str`, _optional_) — If provided, only files matching at least one pattern are downloaded. +* [](#huggingface_hub.HfApi.snapshot_download.ignore_patterns)**ignore\_patterns** (`List[str]` or `str`, _optional_) — If provided, files matching any of the patterns are not downloaded. +* [](#huggingface_hub.HfApi.snapshot_download.max_workers)**max\_workers** (`int`, _optional_) — Number of concurrent threads to download files (1 thread = 1 file download). Defaults to 8. +* [](#huggingface_hub.HfApi.snapshot_download.tqdm_class)**tqdm\_class** (`tqdm`, _optional_) — If provided, overwrites the default behavior for the progress bar. Passed argument must inherit from `tqdm.auto.tqdm` or at least mimic its behavior. Note that the `tqdm_class` is not passed to each individual download. Defaults to the custom HF progress bar that can be disabled by setting `HF_HUB_DISABLE_PROGRESS_BARS` environment variable. + +Returns + +export const metadata = 'undefined'; + +`str` + +export const metadata = 'undefined'; + +folder path of the repo snapshot. + +Raises + +export const metadata = 'undefined'; + +[RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) or [RevisionNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RevisionNotFoundError) or `EnvironmentError` or `OSError` or `ValueError` + +export const metadata = 'undefined'; + +* [RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) — If the repository to download from cannot be found. This may be because it doesn’t exist, or because it is set to `private` and you do not have access. +* [RevisionNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RevisionNotFoundError) — If the revision to download from cannot be found. +* [`EnvironmentError`](https://docs.python.org/3/library/exceptions.html#EnvironmentError) — If `token=True` and the token cannot be found. +* [`OSError`](https://docs.python.org/3/library/exceptions.html#OSError) — if ETag cannot be determined. +* [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError) — if some parameter value is invalid. + +Download repo files. + +Download a whole snapshot of a repo’s files at the specified revision. This is useful when you want all files from a repo, because you don’t know which ones you will need a priori. All files are nested inside a folder in order to keep their actual filename relative to that folder. You can also filter which files to download using `allow_patterns` and `ignore_patterns`. + +If `local_dir` is provided, the file structure from the repo will be replicated in this location. When using this option, the `cache_dir` will not be used and a `.cache/huggingface/` folder will be created at the root of `local_dir` to store some metadata related to the downloaded files.While this mechanism is not as robust as the main cache-system, it’s optimized for regularly pulling the latest version of a repository. + +An alternative would be to clone the repo but this requires git and git-lfs to be installed and properly configured. It is also not possible to filter which files to download when cloning a repository using git. + +#### space\_info + +[](#huggingface_hub.HfApi.space_info)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L2669) + +( repo\_id: strrevision: Optional\[str\] = Nonetimeout: Optional\[float\] = Nonefiles\_metadata: bool = Falseexpand: Optional\[List\[ExpandSpaceProperty\_T\]\] = Nonetoken: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';[SpaceInfo](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.SpaceInfo) + +Expand 6 parameters + +Parameters + +* [](#huggingface_hub.HfApi.space_info.repo_id)**repo\_id** (`str`) — A namespace (user or an organization) and a repo name separated by a `/`. +* [](#huggingface_hub.HfApi.space_info.revision)**revision** (`str`, _optional_) — The revision of the space repository from which to get the information. +* [](#huggingface_hub.HfApi.space_info.timeout)**timeout** (`float`, _optional_) — Whether to set a timeout for the request to the Hub. +* [](#huggingface_hub.HfApi.space_info.files_metadata)**files\_metadata** (`bool`, _optional_) — Whether or not to retrieve metadata for files in the repository (size, LFS metadata, etc). Defaults to `False`. +* [](#huggingface_hub.HfApi.space_info.expand)**expand** (`List[ExpandSpaceProperty_T]`, _optional_) — List properties to return in the response. When used, only the properties in the list will be returned. This parameter cannot be used if `full` is passed. Possible values are `"author"`, `"cardData"`, `"createdAt"`, `"datasets"`, `"disabled"`, `"lastModified"`, `"likes"`, `"models"`, `"private"`, `"runtime"`, `"sdk"`, `"siblings"`, `"sha"`, `"subdomain"`, `"tags"`, `"trendingScore"`, `"usedStorage"`, `"resourceGroup"` and `"xetEnabled"`. +* [](#huggingface_hub.HfApi.space_info.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +[SpaceInfo](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.SpaceInfo) + +export const metadata = 'undefined'; + +The space repository information. + +Get info on one specific Space on huggingface.co. + +Space can be private if you pass an acceptable token. + +Raises the following errors: + +* [RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) If the repository to download from cannot be found. This may be because it doesn’t exist, or because it is set to `private` and you do not have access. +* [RevisionNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RevisionNotFoundError) If the revision to download from cannot be found. + +#### super\_squash\_history + +[](#huggingface_hub.HfApi.super_squash_history)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L3369) + +( repo\_id: strbranch: Optional\[str\] = Nonecommit\_message: Optional\[str\] = Nonerepo\_type: Optional\[str\] = Nonetoken: Union\[str, bool, None\] = None ) + +Expand 5 parameters + +Parameters + +* [](#huggingface_hub.HfApi.super_squash_history.repo_id)**repo\_id** (`str`) — A namespace (user or an organization) and a repo name separated by a `/`. +* [](#huggingface_hub.HfApi.super_squash_history.branch)**branch** (`str`, _optional_) — The branch to squash. Defaults to the head of the `"main"` branch. +* [](#huggingface_hub.HfApi.super_squash_history.commit_message)**commit\_message** (`str`, _optional_) — The commit message to use for the squashed commit. +* [](#huggingface_hub.HfApi.super_squash_history.repo_type)**repo\_type** (`str`, _optional_) — Set to `"dataset"` or `"space"` if listing commits from a dataset or a Space, `None` or `"model"` if listing from a model. Default is `None`. +* [](#huggingface_hub.HfApi.super_squash_history.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Raises + +export const metadata = 'undefined'; + +[RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) or [RevisionNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RevisionNotFoundError) or [BadRequestError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.BadRequestError) + +export const metadata = 'undefined'; + +* [RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) — If repository is not found (error 404): wrong repo\_id/repo\_type, private but not authenticated or repo does not exist. +* [RevisionNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RevisionNotFoundError) — If the branch to squash cannot be found. +* [BadRequestError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.BadRequestError) — If invalid reference for a branch. You cannot squash history on tags. + +Squash commit history on a branch for a repo on the Hub. + +Squashing the repo history is useful when you know you’ll make hundreds of commits and you don’t want to clutter the history. Squashing commits can only be performed from the head of a branch. + +Once squashed, the commit history cannot be retrieved. This is a non-revertible operation. + +Once the history of a branch has been squashed, it is not possible to merge it back into another branch since their history will have diverged. + +[](#huggingface_hub.HfApi.super_squash_history.example) + +Example: + +Copied + +\>>> from huggingface\_hub import HfApi +\>>> api = HfApi() + +\# Create repo +\>>> repo\_id = api.create\_repo("test-squash").repo\_id + +\# Make a lot of commits. +\>>> api.upload\_file(repo\_id=repo\_id, path\_in\_repo="file.txt", path\_or\_fileobj=b"content") +\>>> api.upload\_file(repo\_id=repo\_id, path\_in\_repo="lfs.bin", path\_or\_fileobj=b"content") +\>>> api.upload\_file(repo\_id=repo\_id, path\_in\_repo="file.txt", path\_or\_fileobj=b"another\_content") + +\# Squash history +\>>> api.super\_squash\_history(repo\_id=repo\_id) + +#### unlike + +[](#huggingface_hub.HfApi.unlike)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L2352) + +( repo\_id: strtoken: Union\[bool, str, None\] = Nonerepo\_type: Optional\[str\] = None ) + +Parameters + +* [](#huggingface_hub.HfApi.unlike.repo_id)**repo\_id** (`str`) — The repository to unlike. Example: `"user/my-cool-model"`. +* [](#huggingface_hub.HfApi.unlike.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. +* [](#huggingface_hub.HfApi.unlike.repo_type)**repo\_type** (`str`, _optional_) — Set to `"dataset"` or `"space"` if unliking a dataset or space, `None` or `"model"` if unliking a model. Default is `None`. + +Raises + +export const metadata = 'undefined'; + +[RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) + +export const metadata = 'undefined'; + +* [RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) — If repository is not found (error 404): wrong repo\_id/repo\_type, private but not authenticated or repo does not exist. + +Unlike a given repo on the Hub (e.g. remove from favorite list). + +To prevent spam usage, it is not possible to `like` a repository from a script. + +See also [list\_liked\_repos()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.list_liked_repos). + +[](#huggingface_hub.HfApi.unlike.example) + +Example: + +Copied + +\>>> from huggingface\_hub import list\_liked\_repos, unlike +\>>> "gpt2" in list\_liked\_repos().models \# we assume you have already liked gpt2 +True +\>>> unlike("gpt2") +\>>> "gpt2" in list\_liked\_repos().models +False + +#### update\_collection\_item + +[](#huggingface_hub.HfApi.update_collection_item)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L8473) + +( collection\_slug: stritem\_object\_id: strnote: Optional\[str\] = Noneposition: Optional\[int\] = Nonetoken: Union\[bool, str, None\] = None ) + +Parameters + +* [](#huggingface_hub.HfApi.update_collection_item.collection_slug)**collection\_slug** (`str`) — Slug of the collection to update. Example: `"TheBloke/recent-models-64f9a55bb3115b4f513ec026"`. +* [](#huggingface_hub.HfApi.update_collection_item.item_object_id)**item\_object\_id** (`str`) — ID of the item in the collection. This is not the id of the item on the Hub (repo\_id or paper id). It must be retrieved from a [CollectionItem](/docs/huggingface_hub/v0.30.2/en/package_reference/collections#huggingface_hub.CollectionItem) object. Example: `collection.items[0].item_object_id`. +* [](#huggingface_hub.HfApi.update_collection_item.note)**note** (`str`, _optional_) — A note to attach to the item in the collection. The maximum size for a note is 500 characters. +* [](#huggingface_hub.HfApi.update_collection_item.position)**position** (`int`, _optional_) — New position of the item in the collection. +* [](#huggingface_hub.HfApi.update_collection_item.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Update an item in a collection. + +[](#huggingface_hub.HfApi.update_collection_item.example) + +Example: + +Copied + +\>>> from huggingface\_hub import get\_collection, update\_collection\_item + +\# Get collection first +\>>> collection = get\_collection("TheBloke/recent-models-64f9a55bb3115b4f513ec026") + +\# Update item based on its ID (add note + update position) +\>>> update\_collection\_item( +... collection\_slug="TheBloke/recent-models-64f9a55bb3115b4f513ec026", +... item\_object\_id=collection.items\[-1\].item\_object\_id, +... note="Newly updated model!" +... position=0, +... ) + +#### update\_collection\_metadata + +[](#huggingface_hub.HfApi.update_collection_metadata)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L8282) + +( collection\_slug: strtitle: Optional\[str\] = Nonedescription: Optional\[str\] = Noneposition: Optional\[int\] = Noneprivate: Optional\[bool\] = Nonetheme: Optional\[str\] = Nonetoken: Union\[bool, str, None\] = None ) + +Parameters + +* [](#huggingface_hub.HfApi.update_collection_metadata.collection_slug)**collection\_slug** (`str`) — Slug of the collection to update. Example: `"TheBloke/recent-models-64f9a55bb3115b4f513ec026"`. +* [](#huggingface_hub.HfApi.update_collection_metadata.title)**title** (`str`) — Title of the collection to update. +* [](#huggingface_hub.HfApi.update_collection_metadata.description)**description** (`str`, _optional_) — Description of the collection to update. +* [](#huggingface_hub.HfApi.update_collection_metadata.position)**position** (`int`, _optional_) — New position of the collection in the list of collections of the user. +* [](#huggingface_hub.HfApi.update_collection_metadata.private)**private** (`bool`, _optional_) — Whether the collection should be private or not. +* [](#huggingface_hub.HfApi.update_collection_metadata.theme)**theme** (`str`, _optional_) — Theme of the collection on the Hub. +* [](#huggingface_hub.HfApi.update_collection_metadata.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Update metadata of a collection on the Hub. + +All arguments are optional. Only provided metadata will be updated. + +Returns: [Collection](/docs/huggingface_hub/v0.30.2/en/package_reference/collections#huggingface_hub.Collection) + +[](#huggingface_hub.HfApi.update_collection_metadata.example) + +Example: + +Copied + +\>>> from huggingface\_hub import update\_collection\_metadata +\>>> collection = update\_collection\_metadata( +... collection\_slug="username/iccv-2023-64f9a55bb3115b4f513ec026", +... title="ICCV Oct. 2023" +... description="Portfolio of models, datasets, papers and demos I presented at ICCV Oct. 2023", +... private=False, +... theme="pink", +... ) +\>>> collection.slug +"username/iccv-oct-2023-64f9a55bb3115b4f513ec026" +\# ^collection slug got updated but not the trailing ID + +#### update\_inference\_endpoint + +[](#huggingface_hub.HfApi.update_inference_endpoint)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L7850) + +( name: straccelerator: Optional\[str\] = Noneinstance\_size: Optional\[str\] = Noneinstance\_type: Optional\[str\] = Nonemin\_replica: Optional\[int\] = Nonemax\_replica: Optional\[int\] = Nonescale\_to\_zero\_timeout: Optional\[int\] = Nonerepository: Optional\[str\] = Noneframework: Optional\[str\] = Nonerevision: Optional\[str\] = Nonetask: Optional\[str\] = Nonecustom\_image: Optional\[Dict\] = Nonesecrets: Optional\[Dict\[str, str\]\] = Nonenamespace: Optional\[str\] = Nonetoken: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';[InferenceEndpoint](/docs/huggingface_hub/v0.30.2/en/package_reference/inference_endpoints#huggingface_hub.InferenceEndpoint) + +Expand 15 parameters + +Parameters + +* [](#huggingface_hub.HfApi.update_inference_endpoint.name)**name** (`str`) — The name of the Inference Endpoint to update. +* [](#huggingface_hub.HfApi.update_inference_endpoint.accelerator)**accelerator** (`str`, _optional_) — The hardware accelerator to be used for inference (e.g. `"cpu"`). +* [](#huggingface_hub.HfApi.update_inference_endpoint.instance_size)**instance\_size** (`str`, _optional_) — The size or type of the instance to be used for hosting the model (e.g. `"x4"`). +* [](#huggingface_hub.HfApi.update_inference_endpoint.instance_type)**instance\_type** (`str`, _optional_) — The cloud instance type where the Inference Endpoint will be deployed (e.g. `"intel-icl"`). +* [](#huggingface_hub.HfApi.update_inference_endpoint.min_replica)**min\_replica** (`int`, _optional_) — The minimum number of replicas (instances) to keep running for the Inference Endpoint. +* [](#huggingface_hub.HfApi.update_inference_endpoint.max_replica)**max\_replica** (`int`, _optional_) — The maximum number of replicas (instances) to scale to for the Inference Endpoint. +* [](#huggingface_hub.HfApi.update_inference_endpoint.scale_to_zero_timeout)**scale\_to\_zero\_timeout** (`int`, _optional_) — The duration in minutes before an inactive endpoint is scaled to zero. +* [](#huggingface_hub.HfApi.update_inference_endpoint.repository)**repository** (`str`, _optional_) — The name of the model repository associated with the Inference Endpoint (e.g. `"gpt2"`). +* [](#huggingface_hub.HfApi.update_inference_endpoint.framework)**framework** (`str`, _optional_) — The machine learning framework used for the model (e.g. `"custom"`). +* [](#huggingface_hub.HfApi.update_inference_endpoint.revision)**revision** (`str`, _optional_) — The specific model revision to deploy on the Inference Endpoint (e.g. `"6c0e6080953db56375760c0471a8c5f2929baf11"`). +* [](#huggingface_hub.HfApi.update_inference_endpoint.task)**task** (`str`, _optional_) — The task on which to deploy the model (e.g. `"text-classification"`). +* [](#huggingface_hub.HfApi.update_inference_endpoint.custom_image)**custom\_image** (`Dict`, _optional_) — A custom Docker image to use for the Inference Endpoint. This is useful if you want to deploy an Inference Endpoint running on the `text-generation-inference` (TGI) framework (see examples). +* [](#huggingface_hub.HfApi.update_inference_endpoint.secrets)**secrets** (`Dict[str, str]`, _optional_) — Secret values to inject in the container environment. +* [](#huggingface_hub.HfApi.update_inference_endpoint.namespace)**namespace** (`str`, _optional_) — The namespace where the Inference Endpoint will be updated. Defaults to the current user’s namespace. +* [](#huggingface_hub.HfApi.update_inference_endpoint.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +[InferenceEndpoint](/docs/huggingface_hub/v0.30.2/en/package_reference/inference_endpoints#huggingface_hub.InferenceEndpoint) + +export const metadata = 'undefined'; + +information about the updated Inference Endpoint. + +Update an Inference Endpoint. + +This method allows the update of either the compute configuration, the deployed model, or both. All arguments are optional but at least one must be provided. + +For convenience, you can also update an Inference Endpoint using [InferenceEndpoint.update()](/docs/huggingface_hub/v0.30.2/en/package_reference/inference_endpoints#huggingface_hub.InferenceEndpoint.update). + +#### update\_repo\_settings + +[](#huggingface_hub.HfApi.update_repo_settings)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L3834) + +( repo\_id: strgated: Optional\[Literal\['auto', 'manual', False\]\] = Noneprivate: Optional\[bool\] = Nonetoken: Union\[str, bool, None\] = Nonerepo\_type: Optional\[str\] = Nonexet\_enabled: Optional\[bool\] = None ) + +Expand 6 parameters + +Parameters + +* [](#huggingface_hub.HfApi.update_repo_settings.repo_id)**repo\_id** (`str`) — A namespace (user or an organization) and a repo name separated by a /. +* [](#huggingface_hub.HfApi.update_repo_settings.gated)**gated** (`Literal["auto", "manual", False]`, _optional_) — The gated status for the repository. If set to `None` (default), the `gated` setting of the repository won’t be updated. + + * “auto”: The repository is gated, and access requests are automatically approved or denied based on predefined criteria. + * “manual”: The repository is gated, and access requests require manual approval. + * False : The repository is not gated, and anyone can access it. + +* [](#huggingface_hub.HfApi.update_repo_settings.private)**private** (`bool`, _optional_) — Whether the repository should be private. +* [](#huggingface_hub.HfApi.update_repo_settings.token)**token** (`Union[str, bool, None]`, _optional_) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass False. +* [](#huggingface_hub.HfApi.update_repo_settings.repo_type)**repo\_type** (`str`, _optional_) — The type of the repository to update settings from (`"model"`, `"dataset"` or `"space"`). Defaults to `"model"`. +* [](#huggingface_hub.HfApi.update_repo_settings.xet_enabled)**xet\_enabled** (`bool`, _optional_) — Whether the repository should be enabled for Xet Storage. + +Raises + +export const metadata = 'undefined'; + +`ValueError` or [HfHubHTTPError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.HfHubHTTPError) or [RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) + +export const metadata = 'undefined'; + +* [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError) — If gated is not one of “auto”, “manual”, or False. +* [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError) — If repo\_type is not one of the values in constants.REPO\_TYPES. +* [HfHubHTTPError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.HfHubHTTPError) — If the request to the Hugging Face Hub API fails. +* [RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) — If the repository to download from cannot be found. This may be because it doesn’t exist, or because it is set to `private` and you do not have access. + +Update the settings of a repository, including gated access and visibility. + +To give more control over how repos are used, the Hub allows repo authors to enable access requests for their repos, and also to set the visibility of the repo to private. + +#### update\_repo\_visibility + +[](#huggingface_hub.HfApi.update_repo_visibility)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L3779) + +( repo\_id: strprivate: bool = Falsetoken: Union\[str, bool, None\] = Nonerepo\_type: Optional\[str\] = None ) + +Parameters + +* [](#huggingface_hub.HfApi.update_repo_visibility.repo_id)**repo\_id** (`str`, _optional_) — A namespace (user or an organization) and a repo name separated by a `/`. +* [](#huggingface_hub.HfApi.update_repo_visibility.private)**private** (`bool`, _optional_, defaults to `False`) — Whether the repository should be private. +* [](#huggingface_hub.HfApi.update_repo_visibility.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. +* [](#huggingface_hub.HfApi.update_repo_visibility.repo_type)**repo\_type** (`str`, _optional_) — Set to `"dataset"` or `"space"` if uploading to a dataset or space, `None` or `"model"` if uploading to a model. Default is `None`. + +Update the visibility setting of a repository. + +Deprecated. Use `update_repo_settings` instead. + +Raises the following errors: + +* [RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) If the repository to download from cannot be found. This may be because it doesn’t exist, or because it is set to `private` and you do not have access. + +#### update\_webhook + +[](#huggingface_hub.HfApi.update_webhook)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L9191) + +( webhook\_id: strurl: Optional\[str\] = Nonewatched: Optional\[List\[Union\[Dict, WebhookWatchedItem\]\]\] = Nonedomains: Optional\[List\[constants.WEBHOOK\_DOMAIN\_T\]\] = Nonesecret: Optional\[str\] = Nonetoken: Union\[bool, str, None\] = None ) → export const metadata = 'undefined';[WebhookInfo](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.WebhookInfo) + +Expand 6 parameters + +Parameters + +* [](#huggingface_hub.HfApi.update_webhook.webhook_id)**webhook\_id** (`str`) — The unique identifier of the webhook to be updated. +* [](#huggingface_hub.HfApi.update_webhook.url)**url** (`str`, optional) — The URL to which the payload will be sent. +* [](#huggingface_hub.HfApi.update_webhook.watched)**watched** (`List[WebhookWatchedItem]`, optional) — List of items to watch. It can be users, orgs, models, datasets, or spaces. Refer to [WebhookWatchedItem](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.WebhookWatchedItem) for more details. Watched items can also be provided as plain dictionaries. +* [](#huggingface_hub.HfApi.update_webhook.domains)**domains** (`List[Literal["repo", "discussion"]]`, optional) — The domains to watch. This can include “repo”, “discussion”, or both. +* [](#huggingface_hub.HfApi.update_webhook.secret)**secret** (`str`, optional) — A secret to sign the payload with, providing an additional layer of security. +* [](#huggingface_hub.HfApi.update_webhook.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Returns + +export const metadata = 'undefined'; + +[WebhookInfo](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.WebhookInfo) + +export const metadata = 'undefined'; + +Info about the updated webhook. + +Update an existing webhook. + +[](#huggingface_hub.HfApi.update_webhook.example) + +Example: + +Copied + +\>>> from huggingface\_hub import update\_webhook +\>>> updated\_payload = update\_webhook( +... webhook\_id="654bbbc16f2ec14d77f109cc", +... url="https://new.webhook.site/a2176e82-5720-43ee-9e06-f91cb4c91548", +... watched=\[{"type": "user", "name": "julien-c"}, {"type": "org", "name": "HuggingFaceH4"}\], +... domains=\["repo"\], +... secret="my-secret", +... ) +\>>> print(updated\_payload) +WebhookInfo( + id\="654bbbc16f2ec14d77f109cc", + url="https://new.webhook.site/a2176e82-5720-43ee-9e06-f91cb4c91548", + watched=\[WebhookWatchedItem(type\="user", name="julien-c"), WebhookWatchedItem(type\="org", name="HuggingFaceH4")\], + domains=\["repo"\], + secret="my-secret", + disabled=False, + +#### upload\_file + +[](#huggingface_hub.HfApi.upload_file)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L4523) + +( path\_or\_fileobj: Union\[str, Path, bytes, BinaryIO\]path\_in\_repo: strrepo\_id: strtoken: Union\[str, bool, None\] = Nonerepo\_type: Optional\[str\] = Nonerevision: Optional\[str\] = Nonecommit\_message: Optional\[str\] = Nonecommit\_description: Optional\[str\] = Nonecreate\_pr: Optional\[bool\] = Noneparent\_commit: Optional\[str\] = Nonerun\_as\_future: bool = False ) → export const metadata = 'undefined';[CommitInfo](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.CommitInfo) or `Future` + +Expand 11 parameters + +Parameters + +* [](#huggingface_hub.HfApi.upload_file.path_or_fileobj)**path\_or\_fileobj** (`str`, `Path`, `bytes`, or `IO`) — Path to a file on the local machine or binary data stream / fileobj / buffer. +* [](#huggingface_hub.HfApi.upload_file.path_in_repo)**path\_in\_repo** (`str`) — Relative filepath in the repo, for example: `"checkpoints/1fec34a/weights.bin"` +* [](#huggingface_hub.HfApi.upload_file.repo_id)**repo\_id** (`str`) — The repository to which the file will be uploaded, for example: `"username/custom_transformers"` +* [](#huggingface_hub.HfApi.upload_file.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. +* [](#huggingface_hub.HfApi.upload_file.repo_type)**repo\_type** (`str`, _optional_) — Set to `"dataset"` or `"space"` if uploading to a dataset or space, `None` or `"model"` if uploading to a model. Default is `None`. +* [](#huggingface_hub.HfApi.upload_file.revision)**revision** (`str`, _optional_) — The git revision to commit from. Defaults to the head of the `"main"` branch. +* [](#huggingface_hub.HfApi.upload_file.commit_message)**commit\_message** (`str`, _optional_) — The summary / title / first line of the generated commit +* [](#huggingface_hub.HfApi.upload_file.commit_description)**commit\_description** (`str` _optional_) — The description of the generated commit +* [](#huggingface_hub.HfApi.upload_file.create_pr)**create\_pr** (`boolean`, _optional_) — Whether or not to create a Pull Request with that commit. Defaults to `False`. If `revision` is not set, PR is opened against the `"main"` branch. If `revision` is set and is a branch, PR is opened against this branch. If `revision` is set and is not a branch name (example: a commit oid), an `RevisionNotFoundError` is returned by the server. +* [](#huggingface_hub.HfApi.upload_file.parent_commit)**parent\_commit** (`str`, _optional_) — The OID / SHA of the parent commit, as a hexadecimal string. Shorthands (7 first characters) are also supported. If specified and `create_pr` is `False`, the commit will fail if `revision` does not point to `parent_commit`. If specified and `create_pr` is `True`, the pull request will be created from `parent_commit`. Specifying `parent_commit` ensures the repo has not changed before committing the changes, and can be especially useful if the repo is updated / committed to concurrently. +* [](#huggingface_hub.HfApi.upload_file.run_as_future)**run\_as\_future** (`bool`, _optional_) — Whether or not to run this method in the background. Background jobs are run sequentially without blocking the main thread. Passing `run_as_future=True` will return a [Future](https://docs.python.org/3/library/concurrent.futures.html#future-objects) object. Defaults to `False`. + +Returns + +export const metadata = 'undefined'; + +[CommitInfo](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.CommitInfo) or `Future` + +export const metadata = 'undefined'; + +Instance of [CommitInfo](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.CommitInfo) containing information about the newly created commit (commit hash, commit url, pr url, commit message,…). If `run_as_future=True` is passed, returns a Future object which will contain the result when executed. + +Upload a local file (up to 50 GB) to the given repo. The upload is done through a HTTP post request, and doesn’t require git or git-lfs to be installed. + +Raises the following errors: + +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) if the HuggingFace API returned an error +* [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError) if some parameter value is invalid +* [RepositoryNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RepositoryNotFoundError) If the repository to download from cannot be found. This may be because it doesn’t exist, or because it is set to `private` and you do not have access. +* [RevisionNotFoundError](/docs/huggingface_hub/v0.30.2/en/package_reference/utilities#huggingface_hub.errors.RevisionNotFoundError) If the revision to download from cannot be found. + +`upload_file` assumes that the repo already exists on the Hub. If you get a Client error 404, please make sure you are authenticated and that `repo_id` and `repo_type` are set correctly. If repo does not exist, create it first using [create\_repo()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.create_repo). + +[](#huggingface_hub.HfApi.upload_file.example) + +Example: + +Copied + +\>>> from huggingface\_hub import upload\_file + +\>>> with open("./local/filepath", "rb") as fobj: +... upload\_file( +... path\_or\_fileobj=fileobj, +... path\_in\_repo="remote/file/path.h5", +... repo\_id="username/my-dataset", +... repo\_type="dataset", +... token="my\_token", +... ) +"https://huggingface.co/datasets/username/my-dataset/blob/main/remote/file/path.h5" + +\>>> upload\_file( +... path\_or\_fileobj=".\\\\local\\\\file\\\\path", +... path\_in\_repo="remote/file/path.h5", +... repo\_id="username/my-model", +... token="my\_token", +... ) +"https://huggingface.co/username/my-model/blob/main/remote/file/path.h5" + +\>>> upload\_file( +... path\_or\_fileobj=".\\\\local\\\\file\\\\path", +... path\_in\_repo="remote/file/path.h5", +... repo\_id="username/my-model", +... token="my\_token", +... create\_pr=True, +... ) +"https://huggingface.co/username/my-model/blob/refs%2Fpr%2F1/remote/file/path.h5" + +#### upload\_folder + +[](#huggingface_hub.HfApi.upload_folder)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L4731) + +( repo\_id: strfolder\_path: Union\[str, Path\]path\_in\_repo: Optional\[str\] = Nonecommit\_message: Optional\[str\] = Nonecommit\_description: Optional\[str\] = Nonetoken: Union\[str, bool, None\] = Nonerepo\_type: Optional\[str\] = Nonerevision: Optional\[str\] = Nonecreate\_pr: Optional\[bool\] = Noneparent\_commit: Optional\[str\] = Noneallow\_patterns: Optional\[Union\[List\[str\], str\]\] = Noneignore\_patterns: Optional\[Union\[List\[str\], str\]\] = Nonedelete\_patterns: Optional\[Union\[List\[str\], str\]\] = Nonerun\_as\_future: bool = False ) → export const metadata = 'undefined';[CommitInfo](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.CommitInfo) or `Future` + +Expand 14 parameters + +Parameters + +* [](#huggingface_hub.HfApi.upload_folder.repo_id)**repo\_id** (`str`) — The repository to which the file will be uploaded, for example: `"username/custom_transformers"` +* [](#huggingface_hub.HfApi.upload_folder.folder_path)**folder\_path** (`str` or `Path`) — Path to the folder to upload on the local file system +* [](#huggingface_hub.HfApi.upload_folder.path_in_repo)**path\_in\_repo** (`str`, _optional_) — Relative path of the directory in the repo, for example: `"checkpoints/1fec34a/results"`. Will default to the root folder of the repository. +* [](#huggingface_hub.HfApi.upload_folder.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. +* [](#huggingface_hub.HfApi.upload_folder.repo_type)**repo\_type** (`str`, _optional_) — Set to `"dataset"` or `"space"` if uploading to a dataset or space, `None` or `"model"` if uploading to a model. Default is `None`. +* [](#huggingface_hub.HfApi.upload_folder.revision)**revision** (`str`, _optional_) — The git revision to commit from. Defaults to the head of the `"main"` branch. +* [](#huggingface_hub.HfApi.upload_folder.commit_message)**commit\_message** (`str`, _optional_) — The summary / title / first line of the generated commit. Defaults to: `f"Upload {path_in_repo} with huggingface_hub"` +* [](#huggingface_hub.HfApi.upload_folder.commit_description)**commit\_description** (`str` _optional_) — The description of the generated commit +* [](#huggingface_hub.HfApi.upload_folder.create_pr)**create\_pr** (`boolean`, _optional_) — Whether or not to create a Pull Request with that commit. Defaults to `False`. If `revision` is not set, PR is opened against the `"main"` branch. If `revision` is set and is a branch, PR is opened against this branch. If `revision` is set and is not a branch name (example: a commit oid), an `RevisionNotFoundError` is returned by the server. +* [](#huggingface_hub.HfApi.upload_folder.parent_commit)**parent\_commit** (`str`, _optional_) — The OID / SHA of the parent commit, as a hexadecimal string. Shorthands (7 first characters) are also supported. If specified and `create_pr` is `False`, the commit will fail if `revision` does not point to `parent_commit`. If specified and `create_pr` is `True`, the pull request will be created from `parent_commit`. Specifying `parent_commit` ensures the repo has not changed before committing the changes, and can be especially useful if the repo is updated / committed to concurrently. +* [](#huggingface_hub.HfApi.upload_folder.allow_patterns)**allow\_patterns** (`List[str]` or `str`, _optional_) — If provided, only files matching at least one pattern are uploaded. +* [](#huggingface_hub.HfApi.upload_folder.ignore_patterns)**ignore\_patterns** (`List[str]` or `str`, _optional_) — If provided, files matching any of the patterns are not uploaded. +* [](#huggingface_hub.HfApi.upload_folder.delete_patterns)**delete\_patterns** (`List[str]` or `str`, _optional_) — If provided, remote files matching any of the patterns will be deleted from the repo while committing new files. This is useful if you don’t know which files have already been uploaded. Note: to avoid discrepancies the `.gitattributes` file is not deleted even if it matches the pattern. +* [](#huggingface_hub.HfApi.upload_folder.run_as_future)**run\_as\_future** (`bool`, _optional_) — Whether or not to run this method in the background. Background jobs are run sequentially without blocking the main thread. Passing `run_as_future=True` will return a [Future](https://docs.python.org/3/library/concurrent.futures.html#future-objects) object. Defaults to `False`. + +Returns + +export const metadata = 'undefined'; + +[CommitInfo](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.CommitInfo) or `Future` + +export const metadata = 'undefined'; + +Instance of [CommitInfo](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.CommitInfo) containing information about the newly created commit (commit hash, commit url, pr url, commit message,…). If `run_as_future=True` is passed, returns a Future object which will contain the result when executed. + +Upload a local folder to the given repo. The upload is done through a HTTP requests, and doesn’t require git or git-lfs to be installed. + +The structure of the folder will be preserved. Files with the same name already present in the repository will be overwritten. Others will be left untouched. + +Use the `allow_patterns` and `ignore_patterns` arguments to specify which files to upload. These parameters accept either a single pattern or a list of patterns. Patterns are Standard Wildcards (globbing patterns) as documented [here](https://tldp.org/LDP/GNU-Linux-Tools-Summary/html/x11655.htm). If both `allow_patterns` and `ignore_patterns` are provided, both constraints apply. By default, all files from the folder are uploaded. + +Use the `delete_patterns` argument to specify remote files you want to delete. Input type is the same as for `allow_patterns` (see above). If `path_in_repo` is also provided, the patterns are matched against paths relative to this folder. For example, `upload_folder(..., path_in_repo="experiment", delete_patterns="logs/*")` will delete any remote file under `./experiment/logs/`. Note that the `.gitattributes` file will not be deleted even if it matches the patterns. + +Any `.git/` folder present in any subdirectory will be ignored. However, please be aware that the `.gitignore` file is not taken into account. + +Uses `HfApi.create_commit` under the hood. + +Raises the following errors: + +* [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError) if the HuggingFace API returned an error +* [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError) if some parameter value is invalid + +`upload_folder` assumes that the repo already exists on the Hub. If you get a Client error 404, please make sure you are authenticated and that `repo_id` and `repo_type` are set correctly. If repo does not exist, create it first using [create\_repo()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.create_repo). + +When dealing with a large folder (thousands of files or hundreds of GB), we recommend using [upload\_large\_folder()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.upload_large_folder) instead. + +[](#huggingface_hub.HfApi.upload_folder.example) + +Example: + +Copied + +\# Upload checkpoints folder except the log files +\>>> upload\_folder( +... folder\_path="local/checkpoints", +... path\_in\_repo="remote/experiment/checkpoints", +... repo\_id="username/my-dataset", +... repo\_type="datasets", +... token="my\_token", +... ignore\_patterns="\*\*/logs/\*.txt", +... ) +\# "https://huggingface.co/datasets/username/my-dataset/tree/main/remote/experiment/checkpoints" + +\# Upload checkpoints folder including logs while deleting existing logs from the repo +\# Useful if you don't know exactly which log files have already being pushed +\>>> upload\_folder( +... folder\_path="local/checkpoints", +... path\_in\_repo="remote/experiment/checkpoints", +... repo\_id="username/my-dataset", +... repo\_type="datasets", +... token="my\_token", +... delete\_patterns="\*\*/logs/\*.txt", +... ) +"https://huggingface.co/datasets/username/my-dataset/tree/main/remote/experiment/checkpoints" + +\# Upload checkpoints folder while creating a PR +\>>> upload\_folder( +... folder\_path="local/checkpoints", +... path\_in\_repo="remote/experiment/checkpoints", +... repo\_id="username/my-dataset", +... repo\_type="datasets", +... token="my\_token", +... create\_pr=True, +... ) +"https://huggingface.co/datasets/username/my-dataset/tree/refs%2Fpr%2F1/remote/experiment/checkpoints" + +#### upload\_large\_folder + +[](#huggingface_hub.HfApi.upload_large_folder)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L5198) + +( repo\_id: strfolder\_path: Union\[str, Path\]repo\_type: strrevision: Optional\[str\] = Noneprivate: Optional\[bool\] = Noneallow\_patterns: Optional\[Union\[List\[str\], str\]\] = Noneignore\_patterns: Optional\[Union\[List\[str\], str\]\] = Nonenum\_workers: Optional\[int\] = Noneprint\_report: bool = Trueprint\_report\_every: int = 60 ) + +Expand 10 parameters + +Parameters + +* [](#huggingface_hub.HfApi.upload_large_folder.repo_id)**repo\_id** (`str`) — The repository to which the file will be uploaded. E.g. `"HuggingFaceTB/smollm-corpus"`. +* [](#huggingface_hub.HfApi.upload_large_folder.folder_path)**folder\_path** (`str` or `Path`) — Path to the folder to upload on the local file system. +* [](#huggingface_hub.HfApi.upload_large_folder.repo_type)**repo\_type** (`str`) — Type of the repository. Must be one of `"model"`, `"dataset"` or `"space"`. Unlike in all other `HfApi` methods, `repo_type` is explicitly required here. This is to avoid any mistake when uploading a large folder to the Hub, and therefore prevent from having to re-upload everything. +* [](#huggingface_hub.HfApi.upload_large_folder.revision)**revision** (`str`, `optional`) — The branch to commit to. If not provided, the `main` branch will be used. +* [](#huggingface_hub.HfApi.upload_large_folder.private)**private** (`bool`, `optional`) — Whether the repository should be private. If `None` (default), the repo will be public unless the organization’s default is private. +* [](#huggingface_hub.HfApi.upload_large_folder.allow_patterns)**allow\_patterns** (`List[str]` or `str`, _optional_) — If provided, only files matching at least one pattern are uploaded. +* [](#huggingface_hub.HfApi.upload_large_folder.ignore_patterns)**ignore\_patterns** (`List[str]` or `str`, _optional_) — If provided, files matching any of the patterns are not uploaded. +* [](#huggingface_hub.HfApi.upload_large_folder.num_workers)**num\_workers** (`int`, _optional_) — Number of workers to start. Defaults to `os.cpu_count() - 2` (minimum 2). A higher number of workers may speed up the process if your machine allows it. However, on machines with a slower connection, it is recommended to keep the number of workers low to ensure better resumability. Indeed, partially uploaded files will have to be completely re-uploaded if the process is interrupted. +* [](#huggingface_hub.HfApi.upload_large_folder.print_report)**print\_report** (`bool`, _optional_) — Whether to print a report of the upload progress. Defaults to True. Report is printed to `sys.stdout` every X seconds (60 by defaults) and overwrites the previous report. +* [](#huggingface_hub.HfApi.upload_large_folder.print_report_every)**print\_report\_every** (`int`, _optional_) — Frequency at which the report is printed. Defaults to 60 seconds. + +Upload a large folder to the Hub in the most resilient way possible. + +Several workers are started to upload files in an optimized way. Before being committed to a repo, files must be hashed and be pre-uploaded if they are LFS files. Workers will perform these tasks for each file in the folder. At each step, some metadata information about the upload process is saved in the folder under `.cache/.huggingface/` to be able to resume the process if interrupted. The whole process might result in several commits. + +A few things to keep in mind: + +* Repository limits still apply: [https://huggingface.co/docs/hub/repositories-recommendations](https://huggingface.co/docs/hub/repositories-recommendations) +* Do not start several processes in parallel. +* You can interrupt and resume the process at any time. +* Do not upload the same folder to several repositories. If you need to do so, you must delete the local `.cache/.huggingface/` folder first. + +While being much more robust to upload large folders, `upload_large_folder` is more limited than [upload\_folder()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.upload_folder) feature-wise. In practice: + +* you cannot set a custom `path_in_repo`. If you want to upload to a subfolder, you need to set the proper structure locally. +* you cannot set a custom `commit_message` and `commit_description` since multiple commits are created. +* you cannot delete from the repo while uploading. Please make a separate commit first. +* you cannot create a PR directly. Please create a PR first (from the UI or using [create\_pull\_request()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.create_pull_request)) and then commit to it by passing `revision`. + +**Technical details:** + +`upload_large_folder` process is as follow: + +1. (Check parameters and setup.) +2. Create repo if missing. +3. List local files to upload. +4. Start workers. Workers can perform the following tasks: + * Hash a file. + * Get upload mode (regular or LFS) for a list of files. + * Pre-upload an LFS file. + * Commit a bunch of files. Once a worker finishes a task, it will move on to the next task based on the priority list (see below) until all files are uploaded and committed. +5. While workers are up, regularly print a report to sys.stdout. + +Order of priority: + +1. Commit if more than 5 minutes since last commit attempt (and at least 1 file). +2. Commit if at least 150 files are ready to commit. +3. Get upload mode if at least 10 files have been hashed. +4. Pre-upload LFS file if at least 1 file and no worker is pre-uploading. +5. Hash file if at least 1 file and no worker is hashing. +6. Get upload mode if at least 1 file and no worker is getting upload mode. +7. Pre-upload LFS file if at least 1 file (exception: if hf\_transfer is enabled, only 1 worker can preupload LFS at a time). +8. Hash file if at least 1 file to hash. +9. Get upload mode if at least 1 file to get upload mode. +10. Commit if at least 1 file to commit and at least 1 min since last commit attempt. +11. Commit if at least 1 file to commit and all other queues are empty. + +Special rules: + +* If `hf_transfer` is enabled, only 1 LFS uploader at a time. Otherwise the CPU would be bloated by `hf_transfer`. +* Only one worker can commit at a time. +* If no tasks are available, the worker waits for 10 seconds before checking again. + +#### whoami + +[](#huggingface_hub.HfApi.whoami)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L1718) + +( token: Union\[bool, str, None\] = None ) + +Parameters + +* [](#huggingface_hub.HfApi.whoami.token)**token** (Union\[bool, str, None\], optional) — A valid user access token (string). Defaults to the locally saved token, which is the recommended method for authentication (see [https://huggingface.co/docs/huggingface\_hub/quick-start#authentication](https://huggingface.co/docs/huggingface_hub/quick-start#authentication)). To disable authentication, pass `False`. + +Call HF API to know “whoami”. + +[](#api-dataclasses)API Dataclasses +----------------------------------- + +### [](#huggingface_hub.hf_api.AccessRequest)AccessRequest + +### class huggingface\_hub.hf\_api.AccessRequest + +[](#huggingface_hub.hf_api.AccessRequest)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L452) + +( username: strfullname: stremail: Optional\[str\]timestamp: datetimestatus: Literal\['pending', 'accepted', 'rejected'\]fields: Optional\[Dict\[str, Any\]\] = None ) + +Parameters + +* [](#huggingface_hub.hf_api.AccessRequest.username)**username** (`str`) — Username of the user who requested access. +* [](#huggingface_hub.hf_api.AccessRequest.fullname)**fullname** (`str`) — Fullname of the user who requested access. +* [](#huggingface_hub.hf_api.AccessRequest.email)**email** (`Optional[str]`) — Email of the user who requested access. Can only be `None` in the /accepted list if the user was granted access manually. +* [](#huggingface_hub.hf_api.AccessRequest.timestamp)**timestamp** (`datetime`) — Timestamp of the request. +* [](#huggingface_hub.hf_api.AccessRequest.status)**status** (`Literal["pending", "accepted", "rejected"]`) — Status of the request. Can be one of `["pending", "accepted", "rejected"]`. +* [](#huggingface_hub.hf_api.AccessRequest.fields)**fields** (`Dict[str, Any]`, _optional_) — Additional fields filled by the user in the gate form. + +Data structure containing information about a user access request. + +### [](#huggingface_hub.CommitInfo)CommitInfo + +### class huggingface\_hub.CommitInfo + +[](#huggingface_hub.CommitInfo)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L372) + +( \*argscommit\_url: str\_url: Optional\[str\] = None\*\*kwargs ) + +Parameters + +* [](#huggingface_hub.CommitInfo.commit_url)**commit\_url** (`str`) — Url where to find the commit. +* [](#huggingface_hub.CommitInfo.commit_message)**commit\_message** (`str`) — The summary (first line) of the commit that has been created. +* [](#huggingface_hub.CommitInfo.commit_description)**commit\_description** (`str`) — Description of the commit that has been created. Can be empty. +* [](#huggingface_hub.CommitInfo.oid)**oid** (`str`) — Commit hash id. Example: `"91c54ad1727ee830252e457677f467be0bfd8a57"`. +* [](#huggingface_hub.CommitInfo.pr_url)**pr\_url** (`str`, _optional_) — Url to the PR that has been created, if any. Populated when `create_pr=True` is passed. +* [](#huggingface_hub.CommitInfo.pr_revision)**pr\_revision** (`str`, _optional_) — Revision of the PR that has been created, if any. Populated when `create_pr=True` is passed. Example: `"refs/pr/1"`. +* [](#huggingface_hub.CommitInfo.pr_num)**pr\_num** (`int`, _optional_) — Number of the PR discussion that has been created, if any. Populated when `create_pr=True` is passed. Can be passed as `discussion_num` in [get\_discussion\_details()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.get_discussion_details). Example: `1`. +* [](#huggingface_hub.CommitInfo.repo_url)**repo\_url** (`RepoUrl`) — Repo URL of the commit containing info like repo\_id, repo\_type, etc. +* [](#huggingface_hub.CommitInfo._url)**\_url** (`str`, _optional_) — Legacy url for `str` compatibility. Can be the url to the uploaded file on the Hub (if returned by [upload\_file()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.upload_file)), to the uploaded folder on the Hub (if returned by [upload\_folder()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.upload_folder)) or to the commit on the Hub (if returned by [create\_commit()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.create_commit)). Defaults to `commit_url`. It is deprecated to use this attribute. Please use `commit_url` instead. + +Data structure containing information about a newly created commit. + +Returned by any method that creates a commit on the Hub: [create\_commit()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.create_commit), [upload\_file()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.upload_file), [upload\_folder()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.upload_folder), [delete\_file()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.delete_file), [delete\_folder()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.delete_folder). It inherits from `str` for backward compatibility but using methods specific to `str` is deprecated. + +### [](#huggingface_hub.DatasetInfo)DatasetInfo + +### class huggingface\_hub.DatasetInfo + +[](#huggingface_hub.DatasetInfo)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L910) + +( \*\*kwargs ) + +Expand 16 parameters + +Parameters + +* [](#huggingface_hub.DatasetInfo.id)**id** (`str`) — ID of dataset. +* [](#huggingface_hub.DatasetInfo.author)**author** (`str`) — Author of the dataset. +* [](#huggingface_hub.DatasetInfo.sha)**sha** (`str`) — Repo SHA at this particular revision. +* [](#huggingface_hub.DatasetInfo.created_at)**created\_at** (`datetime`, _optional_) — Date of creation of the repo on the Hub. Note that the lowest value is `2022-03-02T23:29:04.000Z`, corresponding to the date when we began to store creation dates. +* [](#huggingface_hub.DatasetInfo.last_modified)**last\_modified** (`datetime`, _optional_) — Date of last commit to the repo. +* [](#huggingface_hub.DatasetInfo.private)**private** (`bool`) — Is the repo private. +* [](#huggingface_hub.DatasetInfo.disabled)**disabled** (`bool`, _optional_) — Is the repo disabled. +* [](#huggingface_hub.DatasetInfo.gated)**gated** (`Literal["auto", "manual", False]`, _optional_) — Is the repo gated. If so, whether there is manual or automatic approval. +* [](#huggingface_hub.DatasetInfo.downloads)**downloads** (`int`) — Number of downloads of the dataset over the last 30 days. +* [](#huggingface_hub.DatasetInfo.downloads_all_time)**downloads\_all\_time** (`int`) — Cumulated number of downloads of the model since its creation. +* [](#huggingface_hub.DatasetInfo.likes)**likes** (`int`) — Number of likes of the dataset. +* [](#huggingface_hub.DatasetInfo.tags)**tags** (`List[str]`) — List of tags of the dataset. +* [](#huggingface_hub.DatasetInfo.card_data)**card\_data** (`DatasetCardData`, _optional_) — Model Card Metadata as a [huggingface\_hub.repocard\_data.DatasetCardData](/docs/huggingface_hub/v0.30.2/en/package_reference/cards#huggingface_hub.DatasetCardData) object. +* [](#huggingface_hub.DatasetInfo.siblings)**siblings** (`List[RepoSibling]`) — List of [huggingface\_hub.hf\_api.RepoSibling](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.hf_api.RepoSibling) objects that constitute the dataset. +* [](#huggingface_hub.DatasetInfo.paperswithcode_id)**paperswithcode\_id** (`str`, _optional_) — Papers with code ID of the dataset. +* [](#huggingface_hub.DatasetInfo.trending_score)**trending\_score** (`int`, _optional_) — Trending score of the dataset. + +Contains information about a dataset on the Hub. + +Most attributes of this class are optional. This is because the data returned by the Hub depends on the query made. In general, the more specific the query, the more information is returned. On the contrary, when listing datasets using [list\_datasets()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.list_datasets) only a subset of the attributes are returned. + +### [](#huggingface_hub.GitRefInfo)GitRefInfo + +### class huggingface\_hub.GitRefInfo + +[](#huggingface_hub.GitRefInfo)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L1258) + +( name: strref: strtarget\_commit: str ) + +Parameters + +* [](#huggingface_hub.GitRefInfo.name)**name** (`str`) — Name of the reference (e.g. tag name or branch name). +* [](#huggingface_hub.GitRefInfo.ref)**ref** (`str`) — Full git ref on the Hub (e.g. `"refs/heads/main"` or `"refs/tags/v1.0"`). +* [](#huggingface_hub.GitRefInfo.target_commit)**target\_commit** (`str`) — OID of the target commit for the ref (e.g. `"e7da7f221d5bf496a48136c0cd264e630fe9fcc8"`) + +Contains information about a git reference for a repo on the Hub. + +### [](#huggingface_hub.GitCommitInfo)GitCommitInfo + +### class huggingface\_hub.GitCommitInfo + +[](#huggingface_hub.GitCommitInfo)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L1303) + +( commit\_id: strauthors: List\[str\]created\_at: datetimetitle: strmessage: strformatted\_title: Optional\[str\]formatted\_message: Optional\[str\] ) + +Parameters + +* [](#huggingface_hub.GitCommitInfo.commit_id)**commit\_id** (`str`) — OID of the commit (e.g. `"e7da7f221d5bf496a48136c0cd264e630fe9fcc8"`) +* [](#huggingface_hub.GitCommitInfo.authors)**authors** (`List[str]`) — List of authors of the commit. +* [](#huggingface_hub.GitCommitInfo.created_at)**created\_at** (`datetime`) — Datetime when the commit was created. +* [](#huggingface_hub.GitCommitInfo.title)**title** (`str`) — Title of the commit. This is a free-text value entered by the authors. +* [](#huggingface_hub.GitCommitInfo.message)**message** (`str`) — Description of the commit. This is a free-text value entered by the authors. +* [](#huggingface_hub.GitCommitInfo.formatted_title)**formatted\_title** (`str`) — Title of the commit formatted as HTML. Only returned if `formatted=True` is set. +* [](#huggingface_hub.GitCommitInfo.formatted_message)**formatted\_message** (`str`) — Description of the commit formatted as HTML. Only returned if `formatted=True` is set. + +Contains information about a git commit for a repo on the Hub. Check out [list\_repo\_commits()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.list_repo_commits) for more details. + +### [](#huggingface_hub.GitRefs)GitRefs + +### class huggingface\_hub.GitRefs + +[](#huggingface_hub.GitRefs)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L1277) + +( branches: List\[GitRefInfo\]converts: List\[GitRefInfo\]tags: List\[GitRefInfo\]pull\_requests: Optional\[List\[GitRefInfo\]\] = None ) + +Parameters + +* [](#huggingface_hub.GitRefs.branches)**branches** (`List[GitRefInfo]`) — A list of [GitRefInfo](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.GitRefInfo) containing information about branches on the repo. +* [](#huggingface_hub.GitRefs.converts)**converts** (`List[GitRefInfo]`) — A list of [GitRefInfo](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.GitRefInfo) containing information about “convert” refs on the repo. Converts are refs used (internally) to push preprocessed data in Dataset repos. +* [](#huggingface_hub.GitRefs.tags)**tags** (`List[GitRefInfo]`) — A list of [GitRefInfo](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.GitRefInfo) containing information about tags on the repo. +* [](#huggingface_hub.GitRefs.pull_requests)**pull\_requests** (`List[GitRefInfo]`, _optional_) — A list of [GitRefInfo](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.GitRefInfo) containing information about pull requests on the repo. Only returned if `include_prs=True` is set. + +Contains information about all git references for a repo on the Hub. + +Object is returned by [list\_repo\_refs()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.list_repo_refs). + +### [](#huggingface_hub.hf_api.LFSFileInfo)LFSFileInfo + +### class huggingface\_hub.hf\_api.LFSFileInfo + +[](#huggingface_hub.hf_api.LFSFileInfo)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L1536) + +( \*\*kwargs ) + +Parameters + +* [](#huggingface_hub.hf_api.LFSFileInfo.file_oid)**file\_oid** (`str`) — SHA-256 object ID of the file. This is the identifier to pass when permanently deleting the file. +* [](#huggingface_hub.hf_api.LFSFileInfo.filename)**filename** (`str`) — Possible filename for the LFS object. See the note above for more information. +* [](#huggingface_hub.hf_api.LFSFileInfo.oid)**oid** (`str`) — OID of the LFS object. +* [](#huggingface_hub.hf_api.LFSFileInfo.pushed_at)**pushed\_at** (`datetime`) — Date the LFS object was pushed to the repo. +* [](#huggingface_hub.hf_api.LFSFileInfo.ref)**ref** (`str`, _optional_) — Ref where the LFS object has been pushed (if any). +* [](#huggingface_hub.hf_api.LFSFileInfo.size)**size** (`int`) — Size of the LFS object. + +Contains information about a file stored as LFS on a repo on the Hub. + +Used in the context of listing and permanently deleting LFS files from a repo to free-up space. See [list\_lfs\_files()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.list_lfs_files) and [permanently\_delete\_lfs\_files()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.permanently_delete_lfs_files) for more details. + +Git LFS files are tracked using SHA-256 object IDs, rather than file paths, to optimize performance This approach is necessary because a single object can be referenced by multiple paths across different commits, making it impractical to search and resolve these connections. Check out [our documentation](https://huggingface.co/docs/hub/storage-limits#advanced-track-lfs-file-references) to learn how to know which filename(s) is(are) associated with each SHA. + +[](#huggingface_hub.hf_api.LFSFileInfo.example) + +Example: + +Copied + +\>>> from huggingface\_hub import HfApi +\>>> api = HfApi() +\>>> lfs\_files = api.list\_lfs\_files("username/my-cool-repo") + +\# Filter files files to delete based on a combination of \`filename\`, \`pushed\_at\`, \`ref\` or \`size\`. +\# e.g. select only LFS files in the "checkpoints" folder +\>>> lfs\_files\_to\_delete = (lfs\_file for lfs\_file in lfs\_files if lfs\_file.filename.startswith("checkpoints/")) + +\# Permanently delete LFS files +\>>> api.permanently\_delete\_lfs\_files("username/my-cool-repo", lfs\_files\_to\_delete) + +### [](#huggingface_hub.ModelInfo)ModelInfo + +### class huggingface\_hub.ModelInfo + +[](#huggingface_hub.ModelInfo)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L722) + +( \*\*kwargs ) + +Expand 28 parameters + +Parameters + +* [](#huggingface_hub.ModelInfo.id)**id** (`str`) — ID of model. +* [](#huggingface_hub.ModelInfo.author)**author** (`str`, _optional_) — Author of the model. +* [](#huggingface_hub.ModelInfo.sha)**sha** (`str`, _optional_) — Repo SHA at this particular revision. +* [](#huggingface_hub.ModelInfo.created_at)**created\_at** (`datetime`, _optional_) — Date of creation of the repo on the Hub. Note that the lowest value is `2022-03-02T23:29:04.000Z`, corresponding to the date when we began to store creation dates. +* [](#huggingface_hub.ModelInfo.last_modified)**last\_modified** (`datetime`, _optional_) — Date of last commit to the repo. +* [](#huggingface_hub.ModelInfo.private)**private** (`bool`) — Is the repo private. +* [](#huggingface_hub.ModelInfo.disabled)**disabled** (`bool`, _optional_) — Is the repo disabled. +* [](#huggingface_hub.ModelInfo.downloads)**downloads** (`int`) — Number of downloads of the model over the last 30 days. +* [](#huggingface_hub.ModelInfo.downloads_all_time)**downloads\_all\_time** (`int`) — Cumulated number of downloads of the model since its creation. +* [](#huggingface_hub.ModelInfo.gated)**gated** (`Literal["auto", "manual", False]`, _optional_) — Is the repo gated. If so, whether there is manual or automatic approval. +* [](#huggingface_hub.ModelInfo.gguf)**gguf** (`Dict`, _optional_) — GGUF information of the model. +* [](#huggingface_hub.ModelInfo.inference)**inference** (`Literal["cold", "frozen", "warm"]`, _optional_) — Status of the model on the inference API. Warm models are available for immediate use. Cold models will be loaded on first inference call. Frozen models are not available in Inference API. +* [](#huggingface_hub.ModelInfo.inference_provider_mapping)**inference\_provider\_mapping** (`Dict`, _optional_) — Model’s inference provider mapping. +* [](#huggingface_hub.ModelInfo.likes)**likes** (`int`) — Number of likes of the model. +* [](#huggingface_hub.ModelInfo.library_name)**library\_name** (`str`, _optional_) — Library associated with the model. +* [](#huggingface_hub.ModelInfo.tags)**tags** (`List[str]`) — List of tags of the model. Compared to `card_data.tags`, contains extra tags computed by the Hub (e.g. supported libraries, model’s arXiv). +* [](#huggingface_hub.ModelInfo.pipeline_tag)**pipeline\_tag** (`str`, _optional_) — Pipeline tag associated with the model. +* [](#huggingface_hub.ModelInfo.mask_token)**mask\_token** (`str`, _optional_) — Mask token used by the model. +* [](#huggingface_hub.ModelInfo.widget_data)**widget\_data** (`Any`, _optional_) — Widget data associated with the model. +* [](#huggingface_hub.ModelInfo.model_index)**model\_index** (`Dict`, _optional_) — Model index for evaluation. +* [](#huggingface_hub.ModelInfo.config)**config** (`Dict`, _optional_) — Model configuration. +* [](#huggingface_hub.ModelInfo.transformers_info)**transformers\_info** (`TransformersInfo`, _optional_) — Transformers-specific info (auto class, processor, etc.) associated with the model. +* [](#huggingface_hub.ModelInfo.trending_score)**trending\_score** (`int`, _optional_) — Trending score of the model. +* [](#huggingface_hub.ModelInfo.card_data)**card\_data** (`ModelCardData`, _optional_) — Model Card Metadata as a [huggingface\_hub.repocard\_data.ModelCardData](/docs/huggingface_hub/v0.30.2/en/package_reference/cards#huggingface_hub.ModelCardData) object. +* [](#huggingface_hub.ModelInfo.siblings)**siblings** (`List[RepoSibling]`) — List of [huggingface\_hub.hf\_api.RepoSibling](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.hf_api.RepoSibling) objects that constitute the model. +* [](#huggingface_hub.ModelInfo.spaces)**spaces** (`List[str]`, _optional_) — List of spaces using the model. +* [](#huggingface_hub.ModelInfo.safetensors)**safetensors** (`SafeTensorsInfo`, _optional_) — Model’s safetensors information. +* [](#huggingface_hub.ModelInfo.security_repo_status)**security\_repo\_status** (`Dict`, _optional_) — Model’s security scan status. + +Contains information about a model on the Hub. + +Most attributes of this class are optional. This is because the data returned by the Hub depends on the query made. In general, the more specific the query, the more information is returned. On the contrary, when listing models using [list\_models()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.list_models) only a subset of the attributes are returned. + +### [](#huggingface_hub.hf_api.RepoSibling)RepoSibling + +### class huggingface\_hub.hf\_api.RepoSibling + +[](#huggingface_hub.hf_api.RepoSibling)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L585) + +( rfilename: strsize: Optional\[int\] = Noneblob\_id: Optional\[str\] = Nonelfs: Optional\[BlobLfsInfo\] = None ) + +Parameters + +* [](#huggingface_hub.hf_api.RepoSibling.rfilename)**rfilename** (str) — file name, relative to the repo root. +* [](#huggingface_hub.hf_api.RepoSibling.size)**size** (`int`, _optional_) — The file’s size, in bytes. This attribute is defined when `files_metadata` argument of [repo\_info()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.repo_info) is set to `True`. It’s `None` otherwise. +* [](#huggingface_hub.hf_api.RepoSibling.blob_id)**blob\_id** (`str`, _optional_) — The file’s git OID. This attribute is defined when `files_metadata` argument of [repo\_info()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.repo_info) is set to `True`. It’s `None` otherwise. +* [](#huggingface_hub.hf_api.RepoSibling.lfs)**lfs** (`BlobLfsInfo`, _optional_) — The file’s LFS metadata. This attribute is defined when`files_metadata` argument of [repo\_info()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.repo_info) is set to `True` and the file is stored with Git LFS. It’s `None` otherwise. + +Contains basic information about a repo file inside a repo on the Hub. + +All attributes of this class are optional except `rfilename`. This is because only the file names are returned when listing repositories on the Hub (with [list\_models()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.list_models), [list\_datasets()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.list_datasets) or [list\_spaces()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.list_spaces)). If you need more information like file size, blob id or lfs details, you must request them specifically from one repo at a time (using [model\_info()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.model_info), [dataset\_info()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.dataset_info) or [space\_info()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.space_info)) as it adds more constraints on the backend server to retrieve these. + +### [](#huggingface_hub.hf_api.RepoFile)RepoFile + +### class huggingface\_hub.hf\_api.RepoFile + +[](#huggingface_hub.hf_api.RepoFile)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L620) + +( \*\*kwargs ) + +Parameters + +* [](#huggingface_hub.hf_api.RepoFile.path)**path** (str) — file path relative to the repo root. +* [](#huggingface_hub.hf_api.RepoFile.size)**size** (`int`) — The file’s size, in bytes. +* [](#huggingface_hub.hf_api.RepoFile.blob_id)**blob\_id** (`str`) — The file’s git OID. +* [](#huggingface_hub.hf_api.RepoFile.lfs)**lfs** (`BlobLfsInfo`) — The file’s LFS metadata. +* [](#huggingface_hub.hf_api.RepoFile.last_commit)**last\_commit** (`LastCommitInfo`, _optional_) — The file’s last commit metadata. Only defined if [list\_repo\_tree()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.list_repo_tree) and [get\_paths\_info()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.get_paths_info) are called with `expand=True`. +* [](#huggingface_hub.hf_api.RepoFile.security)**security** (`BlobSecurityInfo`, _optional_) — The file’s security scan metadata. Only defined if [list\_repo\_tree()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.list_repo_tree) and [get\_paths\_info()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.get_paths_info) are called with `expand=True`. + +Contains information about a file on the Hub. + +### [](#huggingface_hub.RepoUrl)RepoUrl + +### class huggingface\_hub.RepoUrl + +[](#huggingface_hub.RepoUrl)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L524) + +( url: Anyendpoint: Optional\[str\] = None ) + +Parameters + +* [](#huggingface_hub.RepoUrl.url)**url** (`Any`) — String value of the repo url. +* [](#huggingface_hub.RepoUrl.endpoint)**endpoint** (`str`, _optional_) — Endpoint of the Hub. Defaults to [https://huggingface.co](https://huggingface.co). + +Raises + +export const metadata = 'undefined'; + +`ValueError` + +export const metadata = 'undefined'; + +* [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError) — If URL cannot be parsed. +* [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError) — If `repo_type` is unknown. + +Subclass of `str` describing a repo URL on the Hub. + +`RepoUrl` is returned by `HfApi.create_repo`. It inherits from `str` for backward compatibility. At initialization, the URL is parsed to populate properties: + +* endpoint (`str`) +* namespace (`Optional[str]`) +* repo\_name (`str`) +* repo\_id (`str`) +* repo\_type (`Literal["model", "dataset", "space"]`) +* url (`str`) + +[](#huggingface_hub.RepoUrl.example) + +Example: + +Copied + +\>>> RepoUrl('https://huggingface.co/gpt2') +RepoUrl('https://huggingface.co/gpt2', endpoint='https://huggingface.co', repo\_type='model', repo\_id='gpt2') + +\>>> RepoUrl('https://hub-ci.huggingface.co/datasets/dummy\_user/dummy\_dataset', endpoint='https://hub-ci.huggingface.co') +RepoUrl('https://hub-ci.huggingface.co/datasets/dummy\_user/dummy\_dataset', endpoint='https://hub-ci.huggingface.co', repo\_type='dataset', repo\_id='dummy\_user/dummy\_dataset') + +\>>> RepoUrl('hf://datasets/my-user/my-dataset') +RepoUrl('hf://datasets/my-user/my-dataset', endpoint='https://huggingface.co', repo\_type='dataset', repo\_id='user/dataset') + +\>>> HfApi.create\_repo("dummy\_model") +RepoUrl('https://huggingface.co/Wauplin/dummy\_model', endpoint='https://huggingface.co', repo\_type='model', repo\_id='Wauplin/dummy\_model') + +### [](#huggingface_hub.utils.SafetensorsRepoMetadata)SafetensorsRepoMetadata + +### class huggingface\_hub.utils.SafetensorsRepoMetadata + +[](#huggingface_hub.utils.SafetensorsRepoMetadata)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/utils/_safetensors.py#L73) + +( metadata: typing.Optional\[typing.Dict\]sharded: boolweight\_map: typing.Dict\[str, str\]files\_metadata: typing.Dict\[str, huggingface\_hub.utils.\_safetensors.SafetensorsFileMetadata\] ) + +Parameters + +* [](#huggingface_hub.utils.SafetensorsRepoMetadata.metadata)**metadata** (`Dict`, _optional_) — The metadata contained in the ‘model.safetensors.index.json’ file, if it exists. Only populated for sharded models. +* [](#huggingface_hub.utils.SafetensorsRepoMetadata.sharded)**sharded** (`bool`) — Whether the repo contains a sharded model or not. +* [](#huggingface_hub.utils.SafetensorsRepoMetadata.weight_map)**weight\_map** (`Dict[str, str]`) — A map of all weights. Keys are tensor names and values are filenames of the files containing the tensors. +* [](#huggingface_hub.utils.SafetensorsRepoMetadata.files_metadata)**files\_metadata** (`Dict[str, SafetensorsFileMetadata]`) — A map of all files metadata. Keys are filenames and values are the metadata of the corresponding file, as a `SafetensorsFileMetadata` object. +* [](#huggingface_hub.utils.SafetensorsRepoMetadata.parameter_count)**parameter\_count** (`Dict[str, int]`) — A map of the number of parameters per data type. Keys are data types and values are the number of parameters of that data type. + +Metadata for a Safetensors repo. + +A repo is considered to be a Safetensors repo if it contains either a ‘model.safetensors’ weight file (non-shared model) or a ‘model.safetensors.index.json’ index file (sharded model) at its root. + +This class is returned by [get\_safetensors\_metadata()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.get_safetensors_metadata). + +For more details regarding the safetensors format, check out [https://huggingface.co/docs/safetensors/index#format](https://huggingface.co/docs/safetensors/index#format). + +### [](#huggingface_hub.utils.SafetensorsFileMetadata)SafetensorsFileMetadata + +### class huggingface\_hub.utils.SafetensorsFileMetadata + +[](#huggingface_hub.utils.SafetensorsFileMetadata)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/utils/_safetensors.py#L43) + +( metadata: typing.Dict\[str, str\]tensors: typing.Dict\[str, huggingface\_hub.utils.\_safetensors.TensorInfo\] ) + +Parameters + +* [](#huggingface_hub.utils.SafetensorsFileMetadata.metadata)**metadata** (`Dict`) — The metadata contained in the file. +* [](#huggingface_hub.utils.SafetensorsFileMetadata.tensors)**tensors** (`Dict[str, TensorInfo]`) — A map of all tensors. Keys are tensor names and values are information about the corresponding tensor, as a `TensorInfo` object. +* [](#huggingface_hub.utils.SafetensorsFileMetadata.parameter_count)**parameter\_count** (`Dict[str, int]`) — A map of the number of parameters per data type. Keys are data types and values are the number of parameters of that data type. + +Metadata for a Safetensors file hosted on the Hub. + +This class is returned by [parse\_safetensors\_file\_metadata()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.parse_safetensors_file_metadata). + +For more details regarding the safetensors format, check out [https://huggingface.co/docs/safetensors/index#format](https://huggingface.co/docs/safetensors/index#format). + +### [](#huggingface_hub.SpaceInfo)SpaceInfo + +### class huggingface\_hub.SpaceInfo + +[](#huggingface_hub.SpaceInfo)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L1029) + +( \*\*kwargs ) + +Expand 19 parameters + +Parameters + +* [](#huggingface_hub.SpaceInfo.id)**id** (`str`) — ID of the Space. +* [](#huggingface_hub.SpaceInfo.author)**author** (`str`, _optional_) — Author of the Space. +* [](#huggingface_hub.SpaceInfo.sha)**sha** (`str`, _optional_) — Repo SHA at this particular revision. +* [](#huggingface_hub.SpaceInfo.created_at)**created\_at** (`datetime`, _optional_) — Date of creation of the repo on the Hub. Note that the lowest value is `2022-03-02T23:29:04.000Z`, corresponding to the date when we began to store creation dates. +* [](#huggingface_hub.SpaceInfo.last_modified)**last\_modified** (`datetime`, _optional_) — Date of last commit to the repo. +* [](#huggingface_hub.SpaceInfo.private)**private** (`bool`) — Is the repo private. +* [](#huggingface_hub.SpaceInfo.gated)**gated** (`Literal["auto", "manual", False]`, _optional_) — Is the repo gated. If so, whether there is manual or automatic approval. +* [](#huggingface_hub.SpaceInfo.disabled)**disabled** (`bool`, _optional_) — Is the Space disabled. +* [](#huggingface_hub.SpaceInfo.host)**host** (`str`, _optional_) — Host URL of the Space. +* [](#huggingface_hub.SpaceInfo.subdomain)**subdomain** (`str`, _optional_) — Subdomain of the Space. +* [](#huggingface_hub.SpaceInfo.likes)**likes** (`int`) — Number of likes of the Space. +* [](#huggingface_hub.SpaceInfo.tags)**tags** (`List[str]`) — List of tags of the Space. +* [](#huggingface_hub.SpaceInfo.siblings)**siblings** (`List[RepoSibling]`) — List of [huggingface\_hub.hf\_api.RepoSibling](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.hf_api.RepoSibling) objects that constitute the Space. +* [](#huggingface_hub.SpaceInfo.card_data)**card\_data** (`SpaceCardData`, _optional_) — Space Card Metadata as a [huggingface\_hub.repocard\_data.SpaceCardData](/docs/huggingface_hub/v0.30.2/en/package_reference/cards#huggingface_hub.SpaceCardData) object. +* [](#huggingface_hub.SpaceInfo.runtime)**runtime** (`SpaceRuntime`, _optional_) — Space runtime information as a [huggingface\_hub.hf\_api.SpaceRuntime](/docs/huggingface_hub/v0.30.2/en/package_reference/space_runtime#huggingface_hub.SpaceRuntime) object. +* [](#huggingface_hub.SpaceInfo.sdk)**sdk** (`str`, _optional_) — SDK used by the Space. +* [](#huggingface_hub.SpaceInfo.models)**models** (`List[str]`, _optional_) — List of models used by the Space. +* [](#huggingface_hub.SpaceInfo.datasets)**datasets** (`List[str]`, _optional_) — List of datasets used by the Space. +* [](#huggingface_hub.SpaceInfo.trending_score)**trending\_score** (`int`, _optional_) — Trending score of the Space. + +Contains information about a Space on the Hub. + +Most attributes of this class are optional. This is because the data returned by the Hub depends on the query made. In general, the more specific the query, the more information is returned. On the contrary, when listing spaces using [list\_spaces()](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi.list_spaces) only a subset of the attributes are returned. + +### [](#huggingface_hub.utils.TensorInfo)TensorInfo + +### class huggingface\_hub.utils.TensorInfo + +[](#huggingface_hub.utils.TensorInfo)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/utils/_safetensors.py#L13) + +( dtype: typing.Literal\['F64', 'F32', 'F16', 'BF16', 'I64', 'I32', 'I16', 'I8', 'U8', 'BOOL'\]shape: typing.List\[int\]data\_offsets: typing.Tuple\[int, int\] ) + +Parameters + +* [](#huggingface_hub.utils.TensorInfo.dtype)**dtype** (`str`) — The data type of the tensor (“F64”, “F32”, “F16”, “BF16”, “I64”, “I32”, “I16”, “I8”, “U8”, “BOOL”). +* [](#huggingface_hub.utils.TensorInfo.shape)**shape** (`List[int]`) — The shape of the tensor. +* [](#huggingface_hub.utils.TensorInfo.data_offsets)**data\_offsets** (`Tuple[int, int]`) — The offsets of the data in the file as a tuple `[BEGIN, END]`. +* [](#huggingface_hub.utils.TensorInfo.parameter_count)**parameter\_count** (`int`) — The number of parameters in the tensor. + +Information about a tensor. + +For more details regarding the safetensors format, check out [https://huggingface.co/docs/safetensors/index#format](https://huggingface.co/docs/safetensors/index#format). + +### [](#huggingface_hub.User)User + +### class huggingface\_hub.User + +[](#huggingface_hub.User)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L1391) + +( \*\*kwargs ) + +Parameters + +* [](#huggingface_hub.User.username)**username** (`str`) — Name of the user on the Hub (unique). +* [](#huggingface_hub.User.fullname)**fullname** (`str`) — User’s full name. +* [](#huggingface_hub.User.avatar_url)**avatar\_url** (`str`) — URL of the user’s avatar. +* [](#huggingface_hub.User.details)**details** (`str`, _optional_) — User’s details. +* [](#huggingface_hub.User.is_following)**is\_following** (`bool`, _optional_) — Whether the authenticated user is following this user. +* [](#huggingface_hub.User.is_pro)**is\_pro** (`bool`, _optional_) — Whether the user is a pro user. +* [](#huggingface_hub.User.num_models)**num\_models** (`int`, _optional_) — Number of models created by the user. +* [](#huggingface_hub.User.num_datasets)**num\_datasets** (`int`, _optional_) — Number of datasets created by the user. +* [](#huggingface_hub.User.num_spaces)**num\_spaces** (`int`, _optional_) — Number of spaces created by the user. +* [](#huggingface_hub.User.num_discussions)**num\_discussions** (`int`, _optional_) — Number of discussions initiated by the user. +* [](#huggingface_hub.User.num_papers)**num\_papers** (`int`, _optional_) — Number of papers authored by the user. +* [](#huggingface_hub.User.num_upvotes)**num\_upvotes** (`int`, _optional_) — Number of upvotes received by the user. +* [](#huggingface_hub.User.num_likes)**num\_likes** (`int`, _optional_) — Number of likes given by the user. +* [](#huggingface_hub.User.num_following)**num\_following** (`int`, _optional_) — Number of users this user is following. +* [](#huggingface_hub.User.num_followers)**num\_followers** (`int`, _optional_) — Number of users following this user. +* [](#huggingface_hub.User.orgs)**orgs** (list of `Organization`) — List of organizations the user is part of. + +Contains information about a user on the Hub. + +### [](#huggingface_hub.UserLikes)UserLikes + +### class huggingface\_hub.UserLikes + +[](#huggingface_hub.UserLikes)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L1336) + +( user: strtotal: intdatasets: List\[str\]models: List\[str\]spaces: List\[str\] ) + +Parameters + +* [](#huggingface_hub.UserLikes.user)**user** (`str`) — Name of the user for which we fetched the likes. +* [](#huggingface_hub.UserLikes.total)**total** (`int`) — Total number of likes. +* [](#huggingface_hub.UserLikes.datasets)**datasets** (`List[str]`) — List of datasets liked by the user (as repo\_ids). +* [](#huggingface_hub.UserLikes.models)**models** (`List[str]`) — List of models liked by the user (as repo\_ids). +* [](#huggingface_hub.UserLikes.spaces)**spaces** (`List[str]`) — List of spaces liked by the user (as repo\_ids). + +Contains information about a user likes on the Hub. + +### [](#huggingface_hub.WebhookInfo)WebhookInfo + +### class huggingface\_hub.WebhookInfo + +[](#huggingface_hub.WebhookInfo)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L497) + +( id: strurl: strwatched: List\[WebhookWatchedItem\]domains: List\[constants.WEBHOOK\_DOMAIN\_T\]secret: Optional\[str\]disabled: bool ) + +Parameters + +* [](#huggingface_hub.WebhookInfo.id)**id** (`str`) — ID of the webhook. +* [](#huggingface_hub.WebhookInfo.url)**url** (`str`) — URL of the webhook. +* [](#huggingface_hub.WebhookInfo.watched)**watched** (`List[WebhookWatchedItem]`) — List of items watched by the webhook, see [WebhookWatchedItem](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.WebhookWatchedItem). +* [](#huggingface_hub.WebhookInfo.domains)**domains** (`List[WEBHOOK_DOMAIN_T]`) — List of domains the webhook is watching. Can be one of `["repo", "discussions"]`. +* [](#huggingface_hub.WebhookInfo.secret)**secret** (`str`, _optional_) — Secret of the webhook. +* [](#huggingface_hub.WebhookInfo.disabled)**disabled** (`bool`) — Whether the webhook is disabled or not. + +Data structure containing information about a webhook. + +### [](#huggingface_hub.WebhookWatchedItem)WebhookWatchedItem + +### class huggingface\_hub.WebhookWatchedItem + +[](#huggingface_hub.WebhookWatchedItem)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/hf_api.py#L482) + +( type: Literal\['dataset', 'model', 'org', 'space', 'user'\]name: str ) + +Parameters + +* [](#huggingface_hub.WebhookWatchedItem.type)**type** (`Literal["dataset", "model", "org", "space", "user"]`) — Type of the item to be watched. Can be one of `["dataset", "model", "org", "space", "user"]`. +* [](#huggingface_hub.WebhookWatchedItem.name)**name** (`str`) — Name of the item to be watched. Can be the username, organization name, model name, dataset name or space name. + +Data structure containing information about the items watched by a webhook. + +[](#huggingface_hub.CommitOperationAdd)CommitOperation +------------------------------------------------------ + +Below are the supported values for `CommitOperation()`: + +### class huggingface\_hub.CommitOperationAdd + +[](#huggingface_hub.CommitOperationAdd)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/_commit_api.py#L124) + +( path\_in\_repo: strpath\_or\_fileobj: typing.Union\[str, pathlib.Path, bytes, typing.BinaryIO\] ) + +Parameters + +* [](#huggingface_hub.CommitOperationAdd.path_in_repo)**path\_in\_repo** (`str`) — Relative filepath in the repo, for example: `"checkpoints/1fec34a/weights.bin"` +* [](#huggingface_hub.CommitOperationAdd.path_or_fileobj)**path\_or\_fileobj** (`str`, `Path`, `bytes`, or `BinaryIO`) — Either: + + * a path to a local file (as `str` or `pathlib.Path`) to upload + * a buffer of bytes (`bytes`) holding the content of the file to upload + * a “file object” (subclass of `io.BufferedIOBase`), typically obtained with `open(path, "rb")`. It must support `seek()` and `tell()` methods. + + +Raises + +export const metadata = 'undefined'; + +`ValueError` + +export const metadata = 'undefined'; + +* [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError) — If `path_or_fileobj` is not one of `str`, `Path`, `bytes` or `io.BufferedIOBase`. +* [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError) — If `path_or_fileobj` is a `str` or `Path` but not a path to an existing file. +* [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError) — If `path_or_fileobj` is a `io.BufferedIOBase` but it doesn’t support both `seek()` and `tell()`. + +Data structure holding necessary info to upload a file to a repository on the Hub. + +#### as\_file + +[](#huggingface_hub.CommitOperationAdd.as_file)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/_commit_api.py#L207) + +( with\_tqdm: bool = False ) + +Parameters + +* [](#huggingface_hub.CommitOperationAdd.as_file.with_tqdm)**with\_tqdm** (`bool`, _optional_, defaults to `False`) — If True, iterating over the file object will display a progress bar. Only works if the file-like object is a path to a file. Pure bytes and buffers are not supported. + +A context manager that yields a file-like object allowing to read the underlying data behind `path_or_fileobj`. + +[](#huggingface_hub.CommitOperationAdd.as_file.example) + +Example: + +Copied + +\>>> operation = CommitOperationAdd( +... path\_in\_repo="remote/dir/weights.h5", +... path\_or\_fileobj="./local/weights.h5", +... ) +CommitOperationAdd(path\_in\_repo='remote/dir/weights.h5', path\_or\_fileobj='./local/weights.h5') + +\>>> with operation.as\_file() as file: +... content = file.read() + +\>>> with operation.as\_file(with\_tqdm=True) as file: +... while True: +... data = file.read(1024) +... if not data: +... break +config.json: 100%|█████████████████████████| 8.19k/8.19k \[00:02<00:00, 3.72kB/s\] + +\>>> with operation.as\_file(with\_tqdm=True) as file: +... requests.put(..., data=file) +config.json: 100%|█████████████████████████| 8.19k/8.19k \[00:02<00:00, 3.72kB/s\] + +#### b64content + +[](#huggingface_hub.CommitOperationAdd.b64content)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/_commit_api.py#L257) + +( ) + +The base64-encoded content of `path_or_fileobj` + +Returns: `bytes` + +### class huggingface\_hub.CommitOperationDelete + +[](#huggingface_hub.CommitOperationDelete)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/_commit_api.py#L57) + +( path\_in\_repo: stris\_folder: typing.Union\[bool, typing.Literal\['auto'\]\] = 'auto' ) + +Parameters + +* [](#huggingface_hub.CommitOperationDelete.path_in_repo)**path\_in\_repo** (`str`) — Relative filepath in the repo, for example: `"checkpoints/1fec34a/weights.bin"` for a file or `"checkpoints/1fec34a/"` for a folder. +* [](#huggingface_hub.CommitOperationDelete.is_folder)**is\_folder** (`bool` or `Literal["auto"]`, _optional_) — Whether the Delete Operation applies to a folder or not. If “auto”, the path type (file or folder) is guessed automatically by looking if path ends with a ”/” (folder) or not (file). To explicitly set the path type, you can set `is_folder=True` or `is_folder=False`. + +Data structure holding necessary info to delete a file or a folder from a repository on the Hub. + +### class huggingface\_hub.CommitOperationCopy + +[](#huggingface_hub.CommitOperationCopy)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/_commit_api.py#L88) + +( src\_path\_in\_repo: strpath\_in\_repo: strsrc\_revision: typing.Optional\[str\] = None\_src\_oid: typing.Optional\[str\] = None\_dest\_oid: typing.Optional\[str\] = None ) + +Parameters + +* [](#huggingface_hub.CommitOperationCopy.src_path_in_repo)**src\_path\_in\_repo** (`str`) — Relative filepath in the repo of the file to be copied, e.g. `"checkpoints/1fec34a/weights.bin"`. +* [](#huggingface_hub.CommitOperationCopy.path_in_repo)**path\_in\_repo** (`str`) — Relative filepath in the repo where to copy the file, e.g. `"checkpoints/1fec34a/weights_copy.bin"`. +* [](#huggingface_hub.CommitOperationCopy.src_revision)**src\_revision** (`str`, _optional_) — The git revision of the file to be copied. Can be any valid git revision. Default to the target commit revision. + +Data structure holding necessary info to copy a file in a repository on the Hub. + +Limitations: + +* Only LFS files can be copied. To copy a regular file, you need to download it locally and re-upload it +* Cross-repository copies are not supported. + +Note: you can combine a [CommitOperationCopy](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.CommitOperationCopy) and a [CommitOperationDelete](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.CommitOperationDelete) to rename an LFS file on the Hub. + +[](#huggingface_hub.CommitScheduler)CommitScheduler +--------------------------------------------------- + +### class huggingface\_hub.CommitScheduler + +[](#huggingface_hub.CommitScheduler)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/_commit_scheduler.py#L29) + +( repo\_id: strfolder\_path: typing.Union\[str, pathlib.Path\]every: typing.Union\[int, float\] = 5path\_in\_repo: typing.Optional\[str\] = Nonerepo\_type: typing.Optional\[str\] = Nonerevision: typing.Optional\[str\] = Noneprivate: typing.Optional\[bool\] = Nonetoken: typing.Optional\[str\] = Noneallow\_patterns: typing.Union\[typing.List\[str\], str, NoneType\] = Noneignore\_patterns: typing.Union\[typing.List\[str\], str, NoneType\] = Nonesquash\_history: bool = Falsehf\_api: typing.Optional\[ForwardRef('HfApi')\] = None ) + +Expand 12 parameters + +Parameters + +* [](#huggingface_hub.CommitScheduler.repo_id)**repo\_id** (`str`) — The id of the repo to commit to. +* [](#huggingface_hub.CommitScheduler.folder_path)**folder\_path** (`str` or `Path`) — Path to the local folder to upload regularly. +* [](#huggingface_hub.CommitScheduler.every)**every** (`int` or `float`, _optional_) — The number of minutes between each commit. Defaults to 5 minutes. +* [](#huggingface_hub.CommitScheduler.path_in_repo)**path\_in\_repo** (`str`, _optional_) — Relative path of the directory in the repo, for example: `"checkpoints/"`. Defaults to the root folder of the repository. +* [](#huggingface_hub.CommitScheduler.repo_type)**repo\_type** (`str`, _optional_) — The type of the repo to commit to. Defaults to `model`. +* [](#huggingface_hub.CommitScheduler.revision)**revision** (`str`, _optional_) — The revision of the repo to commit to. Defaults to `main`. +* [](#huggingface_hub.CommitScheduler.private)**private** (`bool`, _optional_) — Whether to make the repo private. If `None` (default), the repo will be public unless the organization’s default is private. This value is ignored if the repo already exists. +* [](#huggingface_hub.CommitScheduler.token)**token** (`str`, _optional_) — The token to use to commit to the repo. Defaults to the token saved on the machine. +* [](#huggingface_hub.CommitScheduler.allow_patterns)**allow\_patterns** (`List[str]` or `str`, _optional_) — If provided, only files matching at least one pattern are uploaded. +* [](#huggingface_hub.CommitScheduler.ignore_patterns)**ignore\_patterns** (`List[str]` or `str`, _optional_) — If provided, files matching any of the patterns are not uploaded. +* [](#huggingface_hub.CommitScheduler.squash_history)**squash\_history** (`bool`, _optional_) — Whether to squash the history of the repo after each commit. Defaults to `False`. Squashing commits is useful to avoid degraded performances on the repo when it grows too large. +* [](#huggingface_hub.CommitScheduler.hf_api)**hf\_api** (`HfApi`, _optional_) — The [HfApi](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.HfApi) client to use to commit to the Hub. Can be set with custom settings (user agent, token,…). + +Scheduler to upload a local folder to the Hub at regular intervals (e.g. push to hub every 5 minutes). + +The recommended way to use the scheduler is to use it as a context manager. This ensures that the scheduler is properly stopped and the last commit is triggered when the script ends. The scheduler can also be stopped manually with the `stop` method. Checkout the [upload guide](https://huggingface.co/docs/huggingface_hub/guides/upload#scheduled-uploads) to learn more about how to use it. + +[](#huggingface_hub.CommitScheduler.example) + +Example: + +Copied + +\>>> from pathlib import Path +\>>> from huggingface\_hub import CommitScheduler + +\# Scheduler uploads every 10 minutes +\>>> csv\_path = Path("watched\_folder/data.csv") +\>>> CommitScheduler(repo\_id="test\_scheduler", repo\_type="dataset", folder\_path=csv\_path.parent, every=10) + +\>>> with csv\_path.open("a") as f: +... f.write("first line") + +\# Some time later (...) +\>>> with csv\_path.open("a") as f: +... f.write("second line") + +[](#huggingface_hub.CommitScheduler.example-2) + +Example using a context manager: + +Copied + +\>>> from pathlib import Path +\>>> from huggingface\_hub import CommitScheduler + +\>>> with CommitScheduler(repo\_id="test\_scheduler", repo\_type="dataset", folder\_path="watched\_folder", every=10) as scheduler: +... csv\_path = Path("watched\_folder/data.csv") +... with csv\_path.open("a") as f: +... f.write("first line") +... (...) +... with csv\_path.open("a") as f: +... f.write("second line") + +\# Scheduler is now stopped and last commit have been triggered + +#### push\_to\_hub + +[](#huggingface_hub.CommitScheduler.push_to_hub)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/_commit_scheduler.py#L204) + +( ) + +Push folder to the Hub and return the commit info. + +This method is not meant to be called directly. It is run in the background by the scheduler, respecting a queue mechanism to avoid concurrent commits. Making a direct call to the method might lead to concurrency issues. + +The default behavior of `push_to_hub` is to assume an append-only folder. It lists all files in the folder and uploads only changed files. If no changes are found, the method returns without committing anything. If you want to change this behavior, you can inherit from [CommitScheduler](/docs/huggingface_hub/v0.30.2/en/package_reference/hf_api#huggingface_hub.CommitScheduler) and override this method. This can be useful for example to compress data together in a single file before committing. For more details and examples, check out our [integration guide](https://huggingface.co/docs/huggingface_hub/main/en/guides/upload#scheduled-uploads). + +#### stop + +[](#huggingface_hub.CommitScheduler.stop)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/_commit_scheduler.py#L157) + +( ) + +Stop the scheduler. + +A stopped scheduler cannot be restarted. Mostly for tests purposes. + +#### trigger + +[](#huggingface_hub.CommitScheduler.trigger)[< source \>](https://github.com/huggingface/huggingface_hub/blob/v0.30.2/src/huggingface_hub/_commit_scheduler.py#L181) + +( ) + +Trigger a `push_to_hub` and return a future. + +This method is automatically called every `every` minutes. You can also call it manually to trigger a commit immediately, without waiting for the next scheduled commit. + +[< \> Update on GitHub](https://github.com/huggingface/huggingface_hub/blob/main/docs/source/en/package_reference/hf_api.md) + +HfApi Client \ No newline at end of file diff --git a/ios/.gitignore b/ios/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..7a7f9873ad7dceb4dc17087fb06c800fa0191376 --- /dev/null +++ b/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000000000000000000000000000000000000..7c56964006274498b0edaa77763cdd72c6d42b6a --- /dev/null +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 12.0 + + diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000000000000000000000000000000000000..ec97fc6f30212d34d53ab17751bcabdfab1891d4 --- /dev/null +++ b/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000000000000000000000000000000000000..c4855bfe2000b73c8a50783b40073676021cbb68 --- /dev/null +++ b/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/ios/Podfile b/ios/Podfile new file mode 100644 index 0000000000000000000000000000000000000000..d97f17e223fb9b6830509dda6cad72aaf57ea780 --- /dev/null +++ b/ios/Podfile @@ -0,0 +1,44 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '12.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000000000000000000000000000000000000..62faa02ff9d65722cf98320646110dec38a18027 --- /dev/null +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,616 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + DefaultConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.aitube2; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.aitube2.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.aitube2.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.aitube2.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + DefaultConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.aitube2; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + DefaultConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.aitube2; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000000000000000000000000000000000..919434a6254f0e9651f402737811be6634a03e9c --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000000000000000000000000000000000..18d981003d68d0546c4804ac2ff47dd97c6e7921 --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000000000000000000000000000000000000..f9b0d7c5ea15f194be85eb6ee8e6721a87ff4644 --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000000000000000000000000000000000000..8e3ca5dfe1936519e96475be8d0b5ff5faa43727 --- /dev/null +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000000000000000000000000000000000..1d526a16ed0f1cd0c2409d848bf489b93fefa3b2 --- /dev/null +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000000000000000000000000000000000..18d981003d68d0546c4804ac2ff47dd97c6e7921 --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000000000000000000000000000000000000..f9b0d7c5ea15f194be85eb6ee8e6721a87ff4644 --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000000000000000000000000000000000000..626664468b8914efda0addf1322b12b8c0071710 --- /dev/null +++ b/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..d36b1fab2d9dea668a4f83df94d525897d9e68dd --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..705cc8e346060dac80e8daa0469b5ffb04cc8254 --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7770183009e914112de7d8ef1d235a6a30c5834424858e0d2f8253f6b8d31926 +size 10932 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..3a0b367a64d6f96b83f504154449e26b0b3fc5f5 --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cab10a0d391ec5bc09ef50ce49e8ad401cee7ef03707ec0923a222c5c2b3d212 +size 295 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..41715252809bcee40a5bb485e65fd2ed3dd556be --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b9ad02cf6576a04d1b6806ac02a2431481b448dd0c2e505ce25842d1f7c4730b +size 406 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..7b5cad00277eb491eb9705cc08697eaa6314c57a --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c6e6d3b215ae744a9c391f4c4d44157eff5e739d6ad6c39f9bfa5df66dddd267 +size 450 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..183a019dd87690af8e24171a1ccb36651ac21478 --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5dee24dc104ac76dc162e42ae0beb163d426bf365562ee28ba7b3ad368559a60 +size 282 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..1b3ddd451446c736c8d9e42e6b3ca1d3ff356476 --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a9b21eb6f4271385655a8771f76e29eef8c1107d7879cbcfc567e6619d1f716a +size 462 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..3b95b770a3c8221af54b31db4ba50211d038f8ef --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e677d701ffe4af7bc2935098d6b3984cc9ab7ace573e6900955a5535b12410cf +size 704 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..41715252809bcee40a5bb485e65fd2ed3dd556be --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b9ad02cf6576a04d1b6806ac02a2431481b448dd0c2e505ce25842d1f7c4730b +size 406 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..ad2b0e0fb28b8a34ab9e63d722a64f0836f85205 --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7c61c42fc7b657d9cf314d32a4ec458f0647c3aaf360be1b9377857266ec2499 +size 586 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..00ad426994dfdfe804fb6849ff0ae854c631327f --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:19be171481dc71a0b2803ebcd01dd8b0c5fd5778dee34c0a3cabc948c225f24e +size 862 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..00ad426994dfdfe804fb6849ff0ae854c631327f --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:19be171481dc71a0b2803ebcd01dd8b0c5fd5778dee34c0a3cabc948c225f24e +size 862 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..58d87f997e8ffba04cd47e7220a20e97d1e1c430 --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4209a49e44a92ec40a327d3455eb1b1c153ee83d75de1c2be0a12ab18b2ff9de +size 1674 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..48ecfaeffd303bd4a36c9ed52b8aa55bd7677572 --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:836c918cb613249eba0483a6b02fa3df3c1c0a89a315ee4d3b88509b83c7ab73 +size 762 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..40ce59afa8ddb3bfcbb5373bdc7437af6713ccc7 --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:41c7d42f6e61f8fe7f30b1ffa2256aecbc9682be06d18c4a3062043e1a2e547c +size 1226 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..3f6dfcc3eb15a282630e31b02cfef45b9e2fd69d --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5d7e5bdf01b93802bc973345b3a78c038907147625035952a08a115a563b7f81 +size 1418 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..0bedcf2fd46788ae3a01a423467513ff59b5c120 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000000000000000000000000000000000000..6ef7d9c4bb261db4e1185835c6d9716015ea61c0 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:93ae7d494fad0fb30cbf3ae746a39c4bc7a0f8bbf87fbb587a3f3c01f3c5ce20 +size 68 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..6ef7d9c4bb261db4e1185835c6d9716015ea61c0 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:93ae7d494fad0fb30cbf3ae746a39c4bc7a0f8bbf87fbb587a3f3c01f3c5ce20 +size 68 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..6ef7d9c4bb261db4e1185835c6d9716015ea61c0 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:93ae7d494fad0fb30cbf3ae746a39c4bc7a0f8bbf87fbb587a3f3c01f3c5ce20 +size 68 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000000000000000000000000000000000000..89c2725b70f1882be97f5214fafe22d27a0ec01e --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000000000000000000000000000000000000..f2e259c7c9390ff69a6bbe1e0907e6dc366848e7 --- /dev/null +++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000000000000000000000000000000000000..f3c28516fb38e64d88cfcf5fb1791175df078f2f --- /dev/null +++ b/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist new file mode 100644 index 0000000000000000000000000000000000000000..7e4fb86c8dcec7a63ad41eada4f57eac5dc702b7 --- /dev/null +++ b/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Aitube2 + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + aitube2 + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000000000000000000000000000000000000..308a2a560b42f17aaf3c36e4e9c8cd07182fbb7e --- /dev/null +++ b/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/ios/RunnerTests/RunnerTests.swift b/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000000000000000000000000000000000000..86a7c3b1b6119f7dbdb8cec74f1b5b3e076bf949 --- /dev/null +++ b/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/lib/config/README.md b/lib/config/README.md new file mode 100644 index 0000000000000000000000000000000000000000..043cb318aaff28396f3c8e2f0961e3940de05367 --- /dev/null +++ b/lib/config/README.md @@ -0,0 +1,13 @@ +Put secrets in here, used only during development + +eg + +``` +class AiTubeSecrets { + final String huggingfaceToken; + + AiTubeSecrets({ + this.huggingfaceToken = '', + }) +} +``` \ No newline at end of file diff --git a/lib/config/config.dart b/lib/config/config.dart new file mode 100644 index 0000000000000000000000000000000000000000..b6d3a0f07475343ecd20ab65a09b9bf677911627 --- /dev/null +++ b/lib/config/config.dart @@ -0,0 +1,156 @@ +import 'package:flutter/services.dart'; +import 'package:yaml/yaml.dart'; + +class Configuration { + static Configuration? _instance; + static Configuration get instance => _instance ??= Configuration._(); + + late Map _config; + + // Prevent multiple instances + Configuration._(); + + static const String _defaultConfigPath = 'assets/config/default.yaml'; + + Future initialize() async { + // Load default config first + final defaultYaml = await rootBundle.loadString(_defaultConfigPath); + _config = _convertYamlToMap(loadYaml(defaultYaml)); + + // Get custom config path from environment + const customConfigPath = String.fromEnvironment( + 'CONFIG_PATH', + defaultValue: 'assets/config/aitube_low.yaml' + ); + + try { + // Load and merge custom config + final customYaml = await rootBundle.loadString(customConfigPath); + final customConfig = _convertYamlToMap(loadYaml(customYaml)); + _mergeConfig(customConfig); + } catch (e) { + print('Warning: Could not load custom config from $customConfigPath: $e'); + } + } + + Map _convertYamlToMap(YamlMap yamlMap) { + Map result = {}; + for (var entry in yamlMap.entries) { + if (entry.value is YamlMap) { + result[entry.key.toString()] = _convertYamlToMap(entry.value); + } else { + result[entry.key.toString()] = entry.value; + } + } + return result; + } + + void _mergeConfig(Map customConfig) { + for (var entry in customConfig.entries) { + if (entry.value is Map && + _config[entry.key] is Map) { + _config[entry.key] = { + ..._config[entry.key] as Map, + ...entry.value as Map + }; + } else { + _config[entry.key] = entry.value; + } + } + } + + // Getters for configuration values + + String get uiProductName => + _config['ui']['product_name']; + + // how many clips should be stored in advance + int get renderQueueBufferSize => + _config['render_queue']['buffer_size']; + + // how many requests for clips can be run in parallel + int get renderQueueMaxConcurrentGenerations => + _config['render_queue']['max_concurrent_generations']; + + // start playback as soon as we have a certain number of videoclips in memory (eg 25%) + int get minimumBufferPercentToStartPlayback => + _config['render_queue']['minimum_buffer_percent_to_start_playback']; + + // transition time between each clip + // the exit (older) clip will see its playback time reduced by this amount + Duration get transitionBufferDuration => + Duration(milliseconds: _config['video']['transition_buffer_duration_ms']); + + // how long a generated clip should be, in Duration + Duration get originalClipDuration => + Duration(seconds: _config['video']['original_clip_duration_seconds']); + + // The model works on resolutions that are divisible by 32 + // and number of frames that are divisible by 8 + 1 (e.g. 257). + // + // In case the resolution or number of frames are not divisible + // by 32 or 8 + 1, the input will be padded with -1 and then + // cropped to the desired resolution and number of frames. + // + // The model works best on resolutions under 720 x 1280 and + // number of frames below 257. + + // number of inference steps + // this has a direct impact in performance obviously, + // you can try to go to low values like 12 or 14 on "safe bet" prompts, + // but if you need a more uncommon topic, you need to go to 18 steps or more + int get numInferenceSteps => + _config['video']['num_inference_steps']; + + int get guidanceScale => + _config['video']['guidance_scale']; + + // original frame-rate of each clip (before we slow them down) + // in frames per second (so an integer) + int get originalClipFrameRate => + _config['video']['original_clip_frame_rate']; + + int get originalClipWidth => + _config['video']['original_clip_width']; + + int get originalClipHeight => + _config['video']['original_clip_height']; + + // to do more with less, we can slow down the videos (a 3s video will become a 4s video) + // but if you are GPU rich feel feel to play them back at 100% of their speed! + double get clipPlaybackSpeed => + _config['video']['clip_playback_speed'].toDouble(); + + // Computed properties + + // original frame-rate of each clip (before we slow them down) + // in frames (so an integer) + // ----------------------- IMPORTANT -------------------------- + // the model has to use a number of frames that can be divided by 8 + // so originalClipNumberOfFrames might not be the actual/final value + // + // == TLDR / IMPORTANT / TLDR / IMPORTANT == + // this is why sometimes a final clip can be longer or shorter! + // ========================================= + // + // ------------------------------------------------------------ + int get originalClipNumberOfFrames => + originalClipFrameRate * originalClipDuration.inSeconds; + + Duration get originalClipPlaybackDuration => + originalClipDuration - transitionBufferDuration; + + // how long a clip should last during playback, in Duration + // that can be different from its original speed + // for instance if play back a 3 seconds video at 75% speed, we get: + // 3 * (1 / 0.75) = 4 + Duration get actualClipDuration => Duration( + // we use millis for greater precision + // important: we internally use double for the calculation + milliseconds: (originalClipDuration.inMilliseconds.toDouble() * + (1.0 / clipPlaybackSpeed)).round() + ); + + Duration get actualClipPlaybackDuration => + actualClipDuration - transitionBufferDuration; +} \ No newline at end of file diff --git a/lib/config/secrets.sample.dart b/lib/config/secrets.sample.dart new file mode 100644 index 0000000000000000000000000000000000000000..c4d1a5dc556c1636e09eac653bd8856fad70093f --- /dev/null +++ b/lib/config/secrets.sample.dart @@ -0,0 +1,4 @@ +/// SECRETS - KEEP THIS OUT OF GIT +class AiTubeSecrets { + static const huggingfaceToken = ''; +} \ No newline at end of file diff --git a/lib/formats/openclap.dart b/lib/formats/openclap.dart new file mode 100644 index 0000000000000000000000000000000000000000..477645076e1616ceb87f9a1d8d51cd07c55151d3 --- /dev/null +++ b/lib/formats/openclap.dart @@ -0,0 +1,558 @@ +// lib/formats/openclap.dart +import 'dart:convert'; +import 'dart:typed_data'; +import 'dart:math'; +import 'dart:io'; +import 'package:uuid/uuid.dart'; +import 'package:yaml/yaml.dart'; +import 'package:http/http.dart' as http; + +enum ClapFormat { + clap0('clap-0'), + clap0b('clap-0b'); + + final String value; + const ClapFormat(this.value); + + static ClapFormat fromString(String value) { + return ClapFormat.values.firstWhere( + (e) => e.value == value, + orElse: () => ClapFormat.clap0, + ); + } +} + +enum ClapSegmentCategory { + splat('SPLAT'), + mesh('MESH'), + depth('DEPTH'), + effect('EFFECT'), + event('EVENT'), + interface('INTERFACE'), + phenomenon('PHENOMENON'), + video('VIDEO'), + image('IMAGE'), + transition('TRANSITION'), + character('CHARACTER'), + location('LOCATION'), + time('TIME'), + era('ERA'), + lighting('LIGHTING'), + weather('WEATHER'), + action('ACTION'), + music('MUSIC'), + sound('SOUND'), + dialogue('DIALOGUE'), + style('STYLE'), + camera('CAMERA'), + group('GROUP'), + generic('GENERIC'); + + final String value; + const ClapSegmentCategory(this.value); + + // Updated to handle nullable String input + static ClapSegmentCategory fromString(String? value) { + if (value == null) return ClapSegmentCategory.generic; + + return ClapSegmentCategory.values.firstWhere( + (e) => e.value == value.toUpperCase(), + orElse: () => ClapSegmentCategory.generic, + ); + } +} + +enum ClapImageRatio { + landscape('LANDSCAPE'), + portrait('PORTRAIT'), + square('SQUARE'); + + final String value; + const ClapImageRatio(this.value); + + static ClapImageRatio fromString(String? value) { + if (value == null) return ClapImageRatio.landscape; + return ClapImageRatio.values.firstWhere( + (e) => e.value == value.toUpperCase(), + orElse: () => ClapImageRatio.landscape, + ); + } +} + +enum ClapOutputType { + text('TEXT'), + animation('ANIMATION'), + interface('INTERFACE'), + event('EVENT'), + phenomenon('PHENOMENON'), + transition('TRANSITION'), + image('IMAGE'), + imageSegmentation('IMAGE_SEGMENTATION'), + imageDepth('IMAGE_DEPTH'), + video('VIDEO'), + videoSegmentation('VIDEO_SEGMENTATION'), + videoDepth('VIDEO_DEPTH'), + audio('AUDIO'); + + final String value; + const ClapOutputType(this.value); + + static ClapOutputType fromString(String? value) { + if (value == null) return ClapOutputType.text; + return ClapOutputType.values.firstWhere( + (e) => e.value == value.toUpperCase(), + orElse: () => ClapOutputType.text, + ); + } +} + +enum ClapAssetSource { + remote('REMOTE'), + path('PATH'), + data('DATA'), + prompt('PROMPT'), + empty('EMPTY'); + + final String value; + const ClapAssetSource(this.value); + + static ClapAssetSource fromString(String? value) { + if (value == null) return ClapAssetSource.empty; + return ClapAssetSource.values.firstWhere( + (e) => e.value == value.toUpperCase(), + orElse: () => ClapAssetSource.empty, + ); + } +} + +/// Data classes for CLAP structure + +class ClapMeta { + final String id; + final String title; + final String description; + final String caption; + final String licence; + final int bpm; + final double frameRate; + final List tags; + final String thumbnailUrl; + final ClapImageRatio imageRatio; + final int durationInMs; + final int width; + final int height; + final String imagePrompt; + final String systemPrompt; + final String storyPrompt; + final bool isLoop; + final bool isInteractive; + + ClapMeta({ + String? id, + this.title = '', + this.description = '', + this.caption = '', + this.licence = '', + this.bpm = 120, + this.frameRate = 24, + this.tags = const [], + this.thumbnailUrl = '', + ClapImageRatio? imageRatio, + this.durationInMs = 4000, + this.width = 1024, + this.height = 576, + this.imagePrompt = '', + this.systemPrompt = '', + this.storyPrompt = '', + this.isLoop = false, + this.isInteractive = false, + }) : id = id ?? const Uuid().v4(), + imageRatio = imageRatio ?? ClapImageRatio.landscape; + + factory ClapMeta.fromMap(Map map) { + return ClapMeta( + id: map['id'] as String?, + title: map['title'] as String? ?? '', + description: map['description'] as String? ?? '', + caption: map['caption'] as String? ?? '', + licence: map['licence'] as String? ?? '', + bpm: (map['bpm'] as num?)?.toInt() ?? 120, + frameRate: (map['frameRate'] as num?)?.toDouble() ?? 24, + tags: List.from(map['tags'] ?? []), + thumbnailUrl: map['thumbnailUrl'] as String? ?? '', + imageRatio: ClapImageRatio.fromString(map['imageRatio'] as String?), + durationInMs: (map['durationInMs'] as num?)?.toInt() ?? 4000, + width: (map['width'] as num?)?.toInt() ?? 1024, + height: (map['height'] as num?)?.toInt() ?? 576, + imagePrompt: map['imagePrompt'] as String? ?? '', + systemPrompt: map['systemPrompt'] as String? ?? '', + storyPrompt: map['storyPrompt'] as String? ?? '', + isLoop: map['isLoop'] as bool? ?? false, + isInteractive: map['isInteractive'] as bool? ?? false, + ); + } + + Map toMap() { + return { + 'id': id, + 'title': title, + 'description': description, + 'caption': caption, + 'licence': licence, + 'bpm': bpm, + 'frameRate': frameRate, + 'tags': tags, + 'thumbnailUrl': thumbnailUrl, + 'imageRatio': imageRatio.value, + 'durationInMs': durationInMs, + 'width': width, + 'height': height, + 'imagePrompt': imagePrompt, + 'systemPrompt': systemPrompt, + 'storyPrompt': storyPrompt, + 'isLoop': isLoop, + 'isInteractive': isInteractive, + }; + } +} + +class ClapSegment { + final String id; + final String parentId; + final List childrenIds; + final int track; + final int startTimeInMs; + final int endTimeInMs; + final ClapSegmentCategory category; + final String entityId; + final String workflowId; + final String sceneId; + final int startTimeInLines; + final int endTimeInLines; + final String prompt; + final String label; + final ClapOutputType outputType; + final String renderId; + final String status; + final String assetUrl; + final int assetDurationInMs; + final ClapAssetSource assetSourceType; + final String assetFileFormat; + final String createdAt; + final String createdBy; + final int revision; + final String editedBy; + final double outputGain; + final int seed; + + ClapSegment({ + String? id, + this.parentId = '', + this.childrenIds = const [], + this.track = 0, + this.startTimeInMs = 0, + this.endTimeInMs = 0, + ClapSegmentCategory? category, + this.entityId = '', + this.workflowId = '', + this.sceneId = '', + this.startTimeInLines = 0, + this.endTimeInLines = 0, + this.prompt = '', + this.label = '', + ClapOutputType? outputType, + this.renderId = '', + this.status = 'TO_GENERATE', + this.assetUrl = '', + this.assetDurationInMs = 0, + ClapAssetSource? assetSourceType, + this.assetFileFormat = '', + String? createdAt, + this.createdBy = 'ai', + this.revision = 0, + this.editedBy = 'ai', + this.outputGain = 0, + int? seed, + }) : id = id ?? const Uuid().v4(), + category = category ?? ClapSegmentCategory.generic, + outputType = outputType ?? ClapOutputType.text, + assetSourceType = assetSourceType ?? ClapAssetSource.empty, + createdAt = createdAt ?? DateTime.now().toIso8601String(), + seed = seed ?? Random().nextInt(1 << 31); + + factory ClapSegment.fromMap(Map map) { + return ClapSegment( + id: map['id'] as String?, + parentId: map['parentId'] as String? ?? '', + childrenIds: List.from(map['childrenIds'] ?? []), + track: (map['track'] as num?)?.toInt() ?? 0, + startTimeInMs: (map['startTimeInMs'] as num?)?.toInt() ?? 0, + endTimeInMs: (map['endTimeInMs'] as num?)?.toInt() ?? 0, + category: ClapSegmentCategory.fromString(map['category'] as String?), + entityId: map['entityId'] as String? ?? '', + workflowId: map['workflowId'] as String? ?? '', + sceneId: map['sceneId'] as String? ?? '', + startTimeInLines: (map['startTimeInLines'] as num?)?.toInt() ?? 0, + endTimeInLines: (map['endTimeInLines'] as num?)?.toInt() ?? 0, + prompt: map['prompt'] as String? ?? '', + label: map['label'] as String? ?? '', + outputType: ClapOutputType.fromString(map['outputType'] as String?), + renderId: map['renderId'] as String? ?? '', + status: map['status'] as String? ?? 'TO_GENERATE', + assetUrl: map['assetUrl'] as String? ?? '', + assetDurationInMs: (map['assetDurationInMs'] as num?)?.toInt() ?? 0, + assetSourceType: ClapAssetSource.fromString(map['assetSourceType'] as String?), + assetFileFormat: map['assetFileFormat'] as String? ?? '', + createdAt: map['createdAt'] as String?, + createdBy: map['createdBy'] as String? ?? 'ai', + revision: (map['revision'] as num?)?.toInt() ?? 0, + editedBy: map['editedBy'] as String? ?? 'ai', + outputGain: (map['outputGain'] as num?)?.toDouble() ?? 0, + seed: (map['seed'] as num?)?.toInt(), + ); + } + + Map toMap() { + return { + 'id': id, + 'parentId': parentId, + 'childrenIds': childrenIds, + 'track': track, + 'startTimeInMs': startTimeInMs, + 'endTimeInMs': endTimeInMs, + 'category': category.value, + 'entityId': entityId, + 'workflowId': workflowId, + 'sceneId': sceneId, + 'startTimeInLines': startTimeInLines, + 'endTimeInLines': endTimeInLines, + 'prompt': prompt, + 'label': label, + 'outputType': outputType.value, + 'renderId': renderId, + 'status': status, + 'assetUrl': assetUrl, + 'assetDurationInMs': assetDurationInMs, + 'assetSourceType': assetSourceType.value, + 'assetFileFormat': assetFileFormat, + 'createdAt': createdAt, + 'createdBy': createdBy, + 'revision': revision, + 'editedBy': editedBy, + 'outputGain': outputGain, + 'seed': seed, + }; + } +} + +/// Main CLAP parser class + +class ClapParser { + static Future> parseClap(dynamic source, { + bool debug = false, + void Function(double progress, String message)? onProgress, + }) async { + onProgress?.call(0, 'Opening .clap file...'); + + // Handle different input types + String yamlString; + if (source is String) { + if (source.startsWith('data:application/x-gzip;base64,') || + source.startsWith('data:application/octet-stream;base64,')) { + // Handle base64 data URI + yamlString = await _decompressBase64(source); + } else if (source.startsWith('http://') || source.startsWith('https://')) { + // Handle remote URL + onProgress?.call(0.2, 'Downloading .clap file...'); + final response = await http.get(Uri.parse(source)); + if (response.statusCode != 200) { + throw Exception('Failed to download the .clap file'); + } + yamlString = await _decompressBytes(response.bodyBytes); + } else { + // Assume direct YAML string + yamlString = source; + } + } else if (source is Uint8List) { + // Handle compressed bytes + yamlString = await _decompressBytes(source); + } else { + throw Exception('Unsupported source type'); + } + + onProgress?.call(0.4, 'Parsing .clap file...'); + + // Parse YAML + final yaml = loadYaml(yamlString) as YamlList; + if (yaml.length < 2) { + throw Exception('Invalid CLAP file: missing header or metadata'); + } + + // Validate format + final header = yaml[0] as YamlMap; + if (header['format'] != ClapFormat.clap0.value) { + throw Exception('Invalid CLAP format'); + } + + onProgress?.call(0.6, 'Processing metadata...'); + + // Parse metadata + final meta = ClapMeta.fromMap(_yamlToMap(yaml[1] as YamlMap)); + + // Parse segments and other components + final segments = []; + final expectedSegments = (header['numberOfSegments'] as int?) ?? 0; + + onProgress?.call(0.8, 'Processing segments...'); + + for (int i = 2; i < yaml.length && i < (2 + expectedSegments); i++) { + segments.add(ClapSegment.fromMap(_yamlToMap(yaml[i] as YamlMap))); + } + + onProgress?.call(1.0, 'Completed parsing'); + + return { + 'meta': meta, + 'segments': segments, + // Add other components as needed + }; + } + + /// Helper method to decompress base64 data URI + static Future _decompressBase64(String dataUri) async { + final base64Data = dataUri.split(',')[1]; + final bytes = base64Decode(base64Data); + return _decompressBytes(bytes); + } + + /// Helper method to decompress gzipped bytes + static Future _decompressBytes(Uint8List bytes) async { + try { + final decompressed = GZipCodec().decode(bytes); + return utf8.decode(decompressed); + } catch (e) { + throw Exception('Failed to decompress CLAP file: $e'); + } + } + + /// Helper method to convert YamlMap to regular Map + static Map _yamlToMap(YamlMap yaml) { + return Map.from(yaml); + } +} + +/// CLAP Serializer class for creating CLAP files + +class ClapSerializer { + static Future serializeClap(Map clap) async { + final meta = clap['meta'] as ClapMeta; + final segments = (clap['segments'] as List?)?.toList() ?? []; + + // Create header + final header = { + 'format': ClapFormat.clap0.value, + 'numberOfSegments': segments.length, + // Add other counts as needed + }; + + // Create YAML entries + final entries = [ + header, + meta.toMap(), + ...segments.map((s) => s.toMap()), + ]; + + // Convert to YAML string + final yaml = toYamlString(entries); + + // Compress + final compressed = GZipCodec().encode(utf8.encode(yaml)); + return Uint8List.fromList(compressed); + } + + /// Helper method to convert data to YAML string + static String toYamlString(List> entries) { + final buffer = StringBuffer(); + for (final entry in entries) { + buffer.writeln('---'); + _writeYamlMap(entry, buffer); + } + return buffer.toString(); + } + + /// Helper method to write map as YAML + static void _writeYamlMap(Map map, StringBuffer buffer, [String indent = '']) { + for (final entry in map.entries) { + if (entry.value == null) continue; + + if (entry.value is Map) { + buffer.writeln('$indent${entry.key}:'); + _writeYamlMap(entry.value as Map, buffer, '$indent '); + } else if (entry.value is List) { + if ((entry.value as List).isEmpty) { + buffer.writeln('$indent${entry.key}: []'); + } else { + buffer.writeln('$indent${entry.key}:'); + for (final item in entry.value as List) { + if (item is Map) { + buffer.writeln('$indent -'); + _writeYamlMap(item as Map, buffer, '$indent '); + } else { + buffer.writeln('$indent - $item'); + } + } + } + } else { + buffer.writeln('$indent${entry.key}: ${_formatYamlValue(entry.value)}'); + } + } + } + + /// Helper method to format YAML values + static String _formatYamlValue(dynamic value) { + if (value is String) { + if (value.contains('\n') || value.contains(':') || value.contains('#')) { + return '|\n ${value.replaceAll('\n', '\n ')}'; + } + return value.contains(' ') ? '"$value"' : value; + } + return value.toString(); + } +} + +/// Example usage class + +class ClapFile { + static Future fromSource(dynamic source) async { + final parsed = await ClapParser.parseClap(source); + return ClapFile._(parsed); + } + + final ClapMeta meta; + final List segments; + // Add other components as needed + + ClapFile._(Map parsed) + : meta = parsed['meta'] as ClapMeta, + segments = (parsed['segments'] as List?)?.toList() ?? []; + + Future serialize() async { + return ClapSerializer.serializeClap({ + 'meta': meta, + 'segments': segments, + }); + } + + /// Helper method to save to a file + Future saveToFile(String path) async { + final bytes = await serialize(); + await File(path).writeAsBytes(bytes); + } + + /// Helper method to create a data URI + Future toDataUri() async { + final bytes = await serialize(); + final base64 = base64Encode(bytes); + return 'data:application/x-gzip;base64,$base64'; + } +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000000000000000000000000000000000000..6b3f6065e4a1443eca8a5d0ae32268214514a7ca --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,122 @@ +// lib/main.dart +import 'package:aitube2/config/config.dart'; +import 'package:aitube2/services/settings_service.dart'; +import 'package:aitube2/services/websocket_api_service.dart'; +import 'package:aitube2/theme/colors.dart'; +import 'package:aitube2/widgets/maintenance_screen.dart'; +import 'package:flutter/material.dart'; +import 'screens/home_screen.dart'; +import 'services/cache_service.dart'; + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + + await Configuration.instance.initialize(); + + Widget homeWidget = const HomeScreen(); + Exception? connectionError; + + try { + // Initialize services in sequence to ensure proper dependencies + await SettingsService().initialize(); + await CacheService().initialize(); + + // Initialize the WebSocket service + final wsService = WebSocketApiService(); + await wsService.initialize(); + + // Check the current status + if (wsService.status == ConnectionStatus.maintenance || wsService.isInMaintenance) { + homeWidget = const MaintenanceScreen(error: null); + } + + // Listen to connection status changes + wsService.statusStream.listen((status) { + if (status == ConnectionStatus.maintenance) { + // Force update to maintenance screen if server goes into maintenance mode later + runApp(AiTubeApp(home: const MaintenanceScreen(error: null))); + } + }); + + } catch (e) { + debugPrint('Error initializing services: $e'); + connectionError = e is Exception ? e : Exception('$e'); + + // If the error message contains maintenance, show the maintenance screen + if (e.toString().toLowerCase().contains('maintenance')) { + homeWidget = const MaintenanceScreen(error: null); + } else { + homeWidget = MaintenanceScreen(error: connectionError); + } + } + + runApp(AiTubeApp(home: homeWidget)); +} + +class AiTubeApp extends StatelessWidget { + final Widget home; + + const AiTubeApp({super.key, required this.home}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: Configuration.instance.uiProductName, + theme: ThemeData.dark().copyWith( + colorScheme: const ColorScheme.dark( + surface: AiTubeColors.surface, + surfaceContainerHighest: AiTubeColors.surfaceVariant, + primary: AiTubeColors.primary, + onSurface: AiTubeColors.onSurface, + onSurfaceVariant: AiTubeColors.onSurfaceVariant, + ), + scaffoldBackgroundColor: AiTubeColors.background, + cardTheme: CardThemeData( + color: AiTubeColors.surface, + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + appBarTheme: const AppBarTheme( + backgroundColor: AiTubeColors.background, + elevation: 0, + ), + textTheme: const TextTheme( + titleLarge: TextStyle(color: AiTubeColors.onBackground), + titleMedium: TextStyle(color: AiTubeColors.onBackground), + bodyLarge: TextStyle(color: AiTubeColors.onSurface), + bodyMedium: TextStyle(color: AiTubeColors.onSurfaceVariant), + ), + ), + darkTheme: ThemeData.dark().copyWith( + colorScheme: const ColorScheme.dark( + surface: AiTubeColors.surface, + surfaceContainerHighest: AiTubeColors.surfaceVariant, + primary: AiTubeColors.primary, + onSurface: AiTubeColors.onSurface, + onSurfaceVariant: AiTubeColors.onSurfaceVariant, + ), + scaffoldBackgroundColor: AiTubeColors.background, + cardTheme: CardThemeData( + color: AiTubeColors.surface, + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + appBarTheme: const AppBarTheme( + backgroundColor: AiTubeColors.background, + elevation: 0, + ), + textTheme: const TextTheme( + titleLarge: TextStyle(color: AiTubeColors.onBackground), + titleMedium: TextStyle(color: AiTubeColors.onBackground), + bodyLarge: TextStyle(color: AiTubeColors.onSurface), + bodyMedium: TextStyle(color: AiTubeColors.onSurfaceVariant), + ), + ), + home: home, + ); + } +} \ No newline at end of file diff --git a/lib/models/chat_message.dart b/lib/models/chat_message.dart new file mode 100644 index 0000000000000000000000000000000000000000..e1c82882730b65b532d34ec4540fd87f5e725d13 --- /dev/null +++ b/lib/models/chat_message.dart @@ -0,0 +1,81 @@ +// lib/models/chat_message.dart +import 'package:uuid/uuid.dart'; + +class ChatMessage { + final String id; + final String userId; + final String username; + final String content; + final DateTime timestamp; + final String videoId; + final String? color; + + ChatMessage({ + String? id, + required this.userId, + required this.username, + required this.content, + required this.videoId, + this.color, + DateTime? timestamp, + }) : id = id ?? const Uuid().v4(), + timestamp = timestamp ?? DateTime.now(); + + factory ChatMessage.fromJson(Map json) { + // Handle potential null or missing values + final String? id = json['id'] as String?; + final String? userId = json['userId'] as String?; + final String? username = json['username'] as String?; + final String? content = json['content'] as String?; + final String? videoId = json['videoId'] as String?; + final String? color = json['color'] as String?; + + // Validate required fields + if (userId == null || username == null || content == null || videoId == null) { + throw FormatException( + 'Invalid chat message format. Required fields missing: ${[ + if (userId == null) 'userId', + if (username == null) 'username', + if (content == null) 'content', + if (videoId == null) 'videoId', + ].join(', ')}' + ); + } + + // Parse timestamp with fallback + DateTime? timestamp; + final timestampStr = json['timestamp'] as String?; + if (timestampStr != null) { + try { + timestamp = DateTime.parse(timestampStr); + } catch (e) { + print('Error parsing timestamp: $e'); + // Use current time as fallback + timestamp = DateTime.now(); + } + } + + return ChatMessage( + id: id, + userId: userId, + username: username, + content: content, + videoId: videoId, + color: color, + timestamp: timestamp, + ); + } + + Map toJson() => { + 'id': id, + 'userId': userId, + 'username': username, + 'content': content, + 'videoId': videoId, + 'color': color, + 'timestamp': timestamp.toIso8601String(), + }; + + @override + String toString() => 'ChatMessage(id: $id, userId: $userId, username: $username, content: $content, videoId: $videoId)'; +} \ No newline at end of file diff --git a/lib/models/search_state.dart b/lib/models/search_state.dart new file mode 100644 index 0000000000000000000000000000000000000000..89342eb45afca3910a19a6fa47bd1456dbc70986 --- /dev/null +++ b/lib/models/search_state.dart @@ -0,0 +1,28 @@ +// lib/models/search_state.dart +class SearchState { + final String query; + final int resultCount; + final DateTime startTime; + + SearchState({ + required this.query, + this.resultCount = 0, + DateTime? startTime, + }) : startTime = startTime ?? DateTime.now(); + + SearchState copyWith({ + String? query, + int? resultCount, + DateTime? startTime, + }) { + return SearchState( + query: query ?? this.query, + resultCount: resultCount ?? this.resultCount, + startTime: startTime ?? this.startTime, + ); + } + + SearchState incrementCount() { + return copyWith(resultCount: resultCount + 1); + } +} \ No newline at end of file diff --git a/lib/models/video_result.dart b/lib/models/video_result.dart new file mode 100644 index 0000000000000000000000000000000000000000..f7d987a195a58ad09b05b1901a87b8beeb9ba8a0 --- /dev/null +++ b/lib/models/video_result.dart @@ -0,0 +1,94 @@ +import 'package:uuid/uuid.dart'; + +class VideoResult { + final String id; + final String title; + final List tags; + final String description; + final String thumbnailUrl; + final String caption; + final bool isLatent; + + // this is a trick we use for some simulations + // it works well for webcams scenarios where + // we want geometry consistency + final bool useFixedSeed; + final int seed; + + final int views; + final String createdAt; + + VideoResult({ + String? id, + required this.title, + this.tags = const [], + this.description = '', + this.thumbnailUrl = '', + this.caption = '', + this.isLatent = true, + this.useFixedSeed = false, + this.seed = 0, + this.views = 0, + String? createdAt, + }) : id = id ?? const Uuid().v4(), + createdAt = createdAt ?? DateTime.now().toIso8601String(); + + factory VideoResult.fromJson(Map json) { + return VideoResult( + id: json['id'] as String?, + title: json['title'] as String? ?? 'Untitled', + tags: (json['tags'] as List?)?.cast() ?? [], + description: json['description'] as String? ?? '', + thumbnailUrl: json['thumbnailUrl'] as String? ?? '', + caption: json['caption'] as String? ?? '', + isLatent: json['isLatent'] as bool? ?? true, + useFixedSeed: json['useFixedSeed'] as bool? ?? false, + seed: json['seed'] as int? ?? 0, + views: json['views'] as int? ?? 0, + createdAt: json['createdAt'] as String?, + ); + } + + Map toJson() => { + 'id': id, + 'title': title, + 'tags': tags, + 'description': description, + 'thumbnailUrl': thumbnailUrl, + 'caption': caption, + 'isLatent': isLatent, + 'useFixedSeed': useFixedSeed, + 'seed': seed, + 'views': views, + 'createdAt': createdAt, + }; + + /// Create a copy of this VideoResult with the given fields replaced with new values + VideoResult copyWith({ + String? id, + String? title, + List? tags, + String? description, + String? thumbnailUrl, + String? caption, + bool? isLatent, + bool? useFixedSeed, + int? seed, + int? views, + String? createdAt, + }) { + return VideoResult( + id: id ?? this.id, + title: title ?? this.title, + tags: tags ?? this.tags, + description: description ?? this.description, + thumbnailUrl: thumbnailUrl ?? this.thumbnailUrl, + caption: caption ?? this.caption, + isLatent: isLatent ?? this.isLatent, + useFixedSeed: useFixedSeed ?? this.useFixedSeed, + seed: seed ?? this.seed, + views: views ?? this.views, + createdAt: createdAt ?? this.createdAt, + ); + } +} \ No newline at end of file diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart new file mode 100644 index 0000000000000000000000000000000000000000..87be8d26fdddbe688ee41a7b67300ef0a9776ea4 --- /dev/null +++ b/lib/screens/home_screen.dart @@ -0,0 +1,332 @@ +// lib/screens/home_screen.dart +import 'dart:async'; +import 'package:aitube2/config/config.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; +import 'package:aitube2/screens/video_screen.dart'; +import 'package:aitube2/screens/settings_screen.dart'; +import 'package:aitube2/models/video_result.dart'; +import 'package:aitube2/services/websocket_api_service.dart'; +import 'package:aitube2/services/cache_service.dart'; +import 'package:aitube2/widgets/video_card.dart'; +import 'package:aitube2/widgets/search_box.dart'; +import 'package:aitube2/theme/colors.dart'; + +class HomeScreen extends StatefulWidget { + const HomeScreen({super.key}); + + @override + State createState() => _HomeScreenState(); +} + +class _HomeScreenState extends State { + final _searchController = TextEditingController(); + final _websocketService = WebSocketApiService(); + final _cacheService = CacheService(); + List _results = []; + bool _isSearching = false; + String? _currentSearchQuery; + StreamSubscription? _searchSubscription; + static const int maxResults = 4; + + @override + void initState() { + super.initState(); + _initializeWebSocket(); + _setupSearchListener(); + _loadLastResults(); + } + + Future _loadLastResults() async { + try { + // Load most recent search results from cache + final cachedResults = await _cacheService.getCachedSearchResults(''); + if (cachedResults.isNotEmpty && mounted) { + setState(() { + _results = cachedResults.take(maxResults).toList(); + }); + } + } catch (e) { + debugPrint('Error loading cached results: $e'); + } + } + + void _setupSearchListener() { + _searchSubscription = _websocketService.searchStream.listen((result) { + if (mounted) { + setState(() { + if (_results.length < maxResults) { + _results.add(result); + // Cache each result as it comes in + if (_currentSearchQuery != null) { + _cacheService.cacheSearchResult( + _currentSearchQuery!, + result, + _results.length, + ); + } + // Stop search if we've reached max results + if (_results.length >= maxResults) { + _stopSearch(); + } + } + }); + } + }); + } + + void _stopSearch() { + if (_currentSearchQuery != null) { + _websocketService.stopContinuousSearch(_currentSearchQuery!); + setState(() { + _isSearching = false; + _currentSearchQuery = null; + }); + } + } + + Future _initializeWebSocket() async { + try { + await _websocketService.connect(); + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Failed to connect to server: $e'), + duration: const Duration(seconds: 3), + action: SnackBarAction( + label: 'Retry', + onPressed: _initializeWebSocket, + ), + ), + ); + } + } + } + + Widget _buildConnectionStatus() { + return StreamBuilder( + stream: _websocketService.statusStream, + builder: (context, connectionSnapshot) { + return StreamBuilder( + stream: _websocketService.userRoleStream, + builder: (context, roleSnapshot) { + final status = connectionSnapshot.data ?? ConnectionStatus.disconnected; + final userRole = roleSnapshot.data ?? 'anon'; + + final backgroundColor = status == ConnectionStatus.connected + ? Colors.green.withOpacity(0.1) + : status == ConnectionStatus.error + ? Colors.red.withOpacity(0.1) + : Colors.orange.withOpacity(0.1); + + final textAndIconColor = status == ConnectionStatus.connected + ? Colors.green + : status == ConnectionStatus.error + ? Colors.red + : Colors.orange; + + final icon = status == ConnectionStatus.connected + ? Icons.cloud_done + : status == ConnectionStatus.error + ? Icons.cloud_off + : Icons.cloud_sync; + + // Modify the status message to include the user role + String statusMessage; + if (status == ConnectionStatus.connected) { + statusMessage = userRole == 'anon' + ? 'Connected as anon' + : 'Connected as $userRole'; + } else if (status == ConnectionStatus.error) { + statusMessage = 'Disconnected'; + } else { + statusMessage = _websocketService.statusMessage; + } + + return Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon, color: textAndIconColor, size: 20), + const SizedBox(width: 8), + Text( + statusMessage, + style: TextStyle( + color: textAndIconColor, + fontSize: 14, + ), + ), + ], + ), + ); + } + ); + }, + ); + } + + Future _search(String query) async { + final trimmedQuery = query.trim(); + if (trimmedQuery.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Please enter a search query')), + ); + return; + } + + // Clear previous results if query is different + if (_currentSearchQuery != trimmedQuery) { + setState(() { + _results.clear(); + _isSearching = true; + }); + } + + // Stop any existing search + if (_currentSearchQuery != null) { + _websocketService.stopContinuousSearch(_currentSearchQuery!); + } + + try { + // Check connection + if (!_websocketService.isConnected) { + await _websocketService.connect(); + } + + _currentSearchQuery = trimmedQuery; + + // Check cache first + final cachedResults = await _cacheService.getCachedSearchResults(trimmedQuery); + if (cachedResults.isNotEmpty) { + if (mounted) { + setState(() { + _results = cachedResults.take(maxResults).toList(); + }); + } + // If we have max results cached, stop searching + if (cachedResults.length >= maxResults) { + setState(() => _isSearching = false); + return; + } + } + + // Start continuous search + _websocketService.startContinuousSearch(trimmedQuery); + + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Error performing search: $e')), + ); + setState(() => _isSearching = false); + } + } + } + + int _getColumnCount(BuildContext context) { + final width = MediaQuery.of(context).size.width; + if (width >= 1536) { // 2XL + return 6; + } else if (width >= 1280) { // XL + return 5; + } else if (width >= 1024) { // LG + return 4; + } else if (width >= 768) { // MD + return 3; + } else { + return 2; // Default for small screens + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(Configuration.instance.uiProductName), + backgroundColor: AiTubeColors.background, + actions: [ + Padding( + padding: const EdgeInsets.only(right: 8), + child: _buildConnectionStatus(), + ), + IconButton( + icon: const Icon(Icons.settings), + onPressed: () { + _stopSearch(); // Stop search but keep results + Navigator.push( + context, + MaterialPageRoute(builder: (context) => const SettingsScreen()), + ); + }, + ), + ], + ), + body: Column( + children: [ + // Search Bar + Padding( + padding: const EdgeInsets.all(16), + child: SearchBox( + controller: _searchController, + isSearching: _isSearching, + enabled: _websocketService.isConnected, + onSearch: _search, + onCancel: _stopSearch, + ), + ), + + // Results Grid + Expanded( + child: _results.isEmpty + ? Center( + child: Text( + _isSearching + ? 'Generating videos...' + : 'Start by typing a description of the video you want to generate', + style: const TextStyle(color: AiTubeColors.onSurfaceVariant), + textAlign: TextAlign.center, + ), + ) + : MasonryGridView.count( + padding: const EdgeInsets.all(16), + crossAxisCount: _getColumnCount(context), + mainAxisSpacing: 16, + crossAxisSpacing: 16, + itemCount: _results.length, + itemBuilder: (context, index) { + return GestureDetector( + onTap: () { + _stopSearch(); // Stop search but keep results + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => VideoScreen( + video: _results[index], + ), + ), + ); + }, + child: VideoCard(video: _results[index]), + ); + }, + ), + ), + ], + ), + ); + } + + @override + void dispose() { + _searchSubscription?.cancel(); + _searchController.dispose(); + _websocketService.dispose(); + super.dispose(); + } +} \ No newline at end of file diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart new file mode 100644 index 0000000000000000000000000000000000000000..be772901599d6ce9d2fcb5140dc21e885b28210f --- /dev/null +++ b/lib/screens/settings_screen.dart @@ -0,0 +1,212 @@ +import 'package:flutter/material.dart'; +import '../services/cache_service.dart'; +import '../services/settings_service.dart'; +import '../services/websocket_api_service.dart'; +import '../theme/colors.dart'; + +class SettingsScreen extends StatefulWidget { + const SettingsScreen({super.key}); + + @override + State createState() => _SettingsScreenState(); +} + +class _SettingsScreenState extends State { + final _promptController = TextEditingController(); + final _hfApiKeyController = TextEditingController(); + final _settingsService = SettingsService(); + + @override + void initState() { + super.initState(); + _promptController.text = _settingsService.videoPromptPrefix; + _hfApiKeyController.text = _settingsService.huggingfaceApiKey; + } + + @override + void dispose() { + _promptController.dispose(); + _hfApiKeyController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Settings'), + ), + body: StreamBuilder( + stream: CacheService().statsStream, + builder: (context, snapshot) { + final stats = snapshot.data; + + return ListView( + padding: const EdgeInsets.all(16), + children: [ + // API Configuration Card + Card( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'API Configuration', + style: TextStyle( + color: AiTubeColors.onBackground, + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 16), + TextField( + controller: _hfApiKeyController, + decoration: const InputDecoration( + labelText: 'Connect using your Hugging Face API Key (optional)', + helperText: 'Hugging Face members enjoy longer-lasting streaming sessions and higher quality.', + helperMaxLines: 2, + ), + obscureText: true, + onChanged: (value) async { + await _settingsService.setHuggingfaceApiKey(value); + // Reinitialize the websocket connection when the API key changes + final websocket = WebSocketApiService(); + if (websocket.isConnected) { + await websocket.dispose(); + await websocket.connect(); + } + }, + ), + ], + ), + ), + ), + const SizedBox(height: 16), + // Video Prompt Prefix Card + Card( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Video Generation', + style: TextStyle( + color: AiTubeColors.onBackground, + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 16), + TextField( + controller: _promptController, + decoration: const InputDecoration( + labelText: 'Video Prompt Prefix', + helperText: 'Text to prepend to all video generation prompts', + helperMaxLines: 2, + ), + onChanged: (value) { + _settingsService.setVideoPromptPrefix(value); + }, + ), + ], + ), + ), + ), + const SizedBox(height: 16), + // Cache Card (existing code) + Card( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Cache', + style: TextStyle( + color: AiTubeColors.onBackground, + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 16), + _buildStatRow( + 'Items in cache', + '${stats?.totalItems ?? 0}', + ), + const SizedBox(height: 8), + _buildStatRow( + 'Total size', + '${(stats?.totalSizeMB ?? 0).toStringAsFixed(2)} MB', + ), + const SizedBox(height: 16), + FilledButton.icon( + onPressed: () async { + final confirmed = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Clear Cache'), + content: const Text( + 'Are you sure you want to clear all cached data? ' + 'This will remove all saved search results and videos.', + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, false), + child: const Text('Cancel'), + ), + FilledButton( + onPressed: () => Navigator.pop(context, true), + child: const Text('Clear'), + ), + ], + ), + ); + + if (confirmed == true) { + await CacheService().clearCache(); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Cache cleared'), + ), + ); + } + } + }, + icon: const Icon(Icons.delete_outline), + label: const Text('Clear Cache'), + ), + ], + ), + ), + ), + ], + ); + }, + ), + ); + } + + Widget _buildStatRow(String label, String value) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + label, + style: const TextStyle( + color: AiTubeColors.onSurfaceVariant, + ), + ), + Text( + value, + style: const TextStyle( + color: AiTubeColors.onBackground, + fontWeight: FontWeight.w500, + ), + ), + ], + ); + } +} diff --git a/lib/screens/video_screen.dart b/lib/screens/video_screen.dart new file mode 100644 index 0000000000000000000000000000000000000000..14e088c8b5c1087a0cbd6cd7a80d3c2584e609b9 --- /dev/null +++ b/lib/screens/video_screen.dart @@ -0,0 +1,275 @@ +// lib/screens/video_screen.dart +import 'package:aitube2/widgets/chat_widget.dart'; +import 'package:aitube2/widgets/search_box.dart'; +import 'package:flutter/material.dart'; +import '../models/video_result.dart'; +import '../services/websocket_api_service.dart'; +import '../services/cache_service.dart'; +import '../theme/colors.dart'; +import '../widgets/video_player_widget.dart'; + +class VideoScreen extends StatefulWidget { + final VideoResult video; + + const VideoScreen({ + super.key, + required this.video, + }); + + @override + State createState() => _VideoScreenState(); +} + +class _VideoScreenState extends State { + Future? _captionFuture; + final _websocketService = WebSocketApiService(); + final _cacheService = CacheService(); + bool _isConnected = false; + late VideoResult _videoData; + final _searchController = TextEditingController(); + bool _isSearching = false; + + @override + void initState() { + super.initState(); + _videoData = widget.video; + _searchController.text = _videoData.title; + _websocketService.addSubscriber(widget.video.id); + _initializeConnection(); + _loadCachedThumbnail(); + } + + Future _loadCachedThumbnail() async { + final cachedThumbnail = await _cacheService.getThumbnail(_videoData.id); + if (cachedThumbnail != null && mounted) { + setState(() { + _videoData = _videoData.copyWith(thumbnailUrl: cachedThumbnail); + }); + } + } + + Future _initializeConnection() async { + try { + await _websocketService.connect(); + if (mounted) { + setState(() { + _isConnected = true; + _captionFuture = _generateCaption(); + }); + } + } catch (e) { + if (mounted) { + setState(() => _isConnected = false); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Failed to connect to server: $e'), + action: SnackBarAction( + label: 'Retry', + onPressed: _initializeConnection, + ), + ), + ); + } + } + } + + Future _generateCaption() async { + if (!_isConnected) { + return 'Error: Not connected to server'; + } + + try { + return await _websocketService.generateCaption( + _videoData.title, + _videoData.description, + ); + } catch (e) { + return 'Error generating caption: $e'; + } + } + + Future _onVideoSearch(String query) async { + if (!_isConnected) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Not connected to server')), + ); + return; + } + + setState(() => _isSearching = true); + + try { + final result = await _websocketService.search(query); + if (mounted) { + setState(() { + _videoData = result; + _isSearching = false; + }); + } + } catch (e) { + if (mounted) { + setState(() => _isSearching = false); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Error: $e')), + ); + } + } + } + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) { + final isWideScreen = constraints.maxWidth >= 900; + + return Scaffold( + appBar: AppBar( + titleSpacing: 0, + title: Padding( + padding: const EdgeInsets.only(right: 8), + child: SearchBox( + controller: _searchController, + isSearching: _isSearching, + enabled: _isConnected, + onSearch: _onVideoSearch, + onCancel: () { + setState(() => _isSearching = false); + }, + ), + ), + actions: [ + IconButton( + icon: Icon( + _isConnected ? Icons.cloud_done : Icons.cloud_off, + color: _isConnected ? Colors.green : Colors.red, + ), + onPressed: _isConnected ? null : _initializeConnection, + ), + ], + ), + body: SafeArea( + child: isWideScreen + ? Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: _buildMainContent(), + ), + const SizedBox(width: 16), + Padding( + padding: const EdgeInsets.only(right: 16), + child: ChatWidget(videoId: widget.video.id), + ), + ], + ) + : Column( + children: [ + _buildMainContent(), + const SizedBox(height: 16), + // Modified this part + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: ChatWidget( + videoId: widget.video.id, + isCompact: true, + ), + ), + ), + ], + ), + ), + ); + }, + ); + } + + Widget _buildMainContent() { + return SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Video Player + VideoPlayerWidget( + video: _videoData, + initialThumbnailUrl: _videoData.thumbnailUrl, + autoPlay: true, + ), + const SizedBox(height: 16), + + // Collapsible Title and Description Section + _buildCollapsibleInfoSection(), + ], + ), + ); + } + + Widget _buildCollapsibleInfoSection() { + return ExpansionTile( + initiallyExpanded: false, + tilePadding: EdgeInsets.zero, + title: Text( + _videoData.title, + style: Theme.of(context).textTheme.headlineSmall?.copyWith( + color: AiTubeColors.onBackground, + fontWeight: FontWeight.bold, + ), + ), + iconColor: AiTubeColors.primary, + collapsedIconColor: AiTubeColors.primary, + backgroundColor: Colors.transparent, + collapsedBackgroundColor: Colors.transparent, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Tags + if (_videoData.tags.isNotEmpty) ...[ + Wrap( + spacing: 8, + runSpacing: 8, + children: _videoData.tags.map((tag) => Chip( + label: Text(tag), + backgroundColor: AiTubeColors.surface, + labelStyle: const TextStyle(color: AiTubeColors.onSurface), + )).toList(), + ), + const SizedBox(height: 16), + ], + + // Description Section + const Text( + 'Description', + style: TextStyle( + color: AiTubeColors.onBackground, + fontWeight: FontWeight.bold, + fontSize: 18, + ), + ), + const SizedBox(height: 8), + Text( + _videoData.description, + style: const TextStyle( + color: AiTubeColors.onSurface, + height: 1.5, + ), + ), + ], + ), + ], + ); + } + + @override + void dispose() { + // Cancel any pending video-related requests + _websocketService.cancelRequestsForVideo(widget.video.id); + _websocketService.removeSubscriber(widget.video.id); + + // Cleanup other resources + _searchController.dispose(); + super.dispose(); + } + +} \ No newline at end of file diff --git a/lib/services/cache_service.dart b/lib/services/cache_service.dart new file mode 100644 index 0000000000000000000000000000000000000000..bce5aa35e46d0a86005568d960dc43763aaae7a1 --- /dev/null +++ b/lib/services/cache_service.dart @@ -0,0 +1,340 @@ +// lib/services/cache_service.dart +import 'dart:async'; +import 'dart:convert'; +import 'package:flutter/foundation.dart'; +import 'package:shared_preferences/shared_preferences.dart' as prefs; +import 'package:idb_shim/idb_browser.dart' if (dart.library.io) 'package:idb_shim/idb_io.dart'; +import '../models/video_result.dart'; + +/// Storage provider to handle platform differences +abstract class StorageProvider { + Future initialize(); + Future write(String key, Uint8List data); + Future read(String key); + Future delete(String key); + Future clear(); + Future getTotalSize(); +} + +/// IndexedDB implementation for web platform +class WebStorageProvider implements StorageProvider { + late Database _db; + static const String _storeName = 'binary_store'; + + @override + Future initialize() async { + final factory = getIdbFactory()!; + _db = await factory.open( + 'aitube_cache', + version: 1, + onUpgradeNeeded: (VersionChangeEvent event) { + final db = event.database; + db.createObjectStore(_storeName); + }, + ); + } + + @override + Future write(String key, Uint8List data) async { + final txn = _db.transaction(_storeName, 'readwrite'); + final store = txn.objectStore(_storeName); + await store.put(data, key); + } + + @override + Future read(String key) async { + final txn = _db.transaction(_storeName, 'readonly'); + final store = txn.objectStore(_storeName); + final data = await store.getObject(key); + if (data == null) return null; + return data as Uint8List; + } + + @override + Future delete(String key) async { + final txn = _db.transaction(_storeName, 'readwrite'); + final store = txn.objectStore(_storeName); + await store.delete(key); + } + + @override + Future clear() async { + final txn = _db.transaction(_storeName, 'readwrite'); + final store = txn.objectStore(_storeName); + await store.clear(); + } + + @override + Future getTotalSize() async { + final txn = _db.transaction(_storeName, 'readonly'); + final store = txn.objectStore(_storeName); + final keys = await store.getAllKeys(); + int totalSize = 0; + + for (final key in keys) { + final data = await store.getObject(key); + if (data is Uint8List) { + totalSize += data.length; + } + } + + return totalSize; + } +} + +/// Memory-based implementation for web platform +class MemoryStorageProvider implements StorageProvider { + final Map _storage = {}; + + @override + Future initialize() async {} + + @override + Future write(String key, Uint8List data) async { + _storage[key] = data; + } + + @override + Future read(String key) async { + return _storage[key]; + } + + @override + Future delete(String key) async { + _storage.remove(key); + } + + @override + Future clear() async { + _storage.clear(); + } + + @override + Future getTotalSize() async { + return _storage.values.fold(0, (total, data) => total + data.length); + } +} + +class CacheService { + static final CacheService _instance = CacheService._internal(); + factory CacheService() => _instance; + + late final prefs.SharedPreferences _prefs; + late final StorageProvider _storage; + final _statsController = StreamController.broadcast(); + + static const String _metadataPrefix = 'metadata_'; + static const String _videoPrefix = 'video_'; + static const String _thumbnailPrefix = 'thumb_'; + static const Duration _cacheExpiry = Duration(days: 7); + + Stream get statsStream => _statsController.stream; + + CacheService._internal() { + // Use IndexedDB for web, memory storage for testing/development + _storage = kIsWeb ? WebStorageProvider() : MemoryStorageProvider(); + } + + Future initialize() async { + await _storage.initialize(); + _prefs = await prefs.SharedPreferences.getInstance(); + await _cleanExpiredEntries(); + await _updateStats(); + } + + Future _cleanExpiredEntries() async { + final now = DateTime.now(); + final keys = _prefs.getKeys().where((k) => k.startsWith(_metadataPrefix)); + + for (final key in keys) { + final metadata = _prefs.getString(key); + if (metadata != null) { + final data = json.decode(metadata); + final timestamp = DateTime.parse(data['timestamp']); + if (now.difference(timestamp) > _cacheExpiry) { + await _removeEntry(key.substring(_metadataPrefix.length)); + } + } + } + } + + Future _removeEntry(String key) async { + await _prefs.remove('$_metadataPrefix$key'); + await _storage.delete(key); + await _updateStats(); + } + + Future _updateStats() async { + final totalSize = await _storage.getTotalSize(); + final totalItems = _prefs.getKeys() + .where((k) => k.startsWith(_metadataPrefix)) + .length; + + _statsController.add(CacheStats( + totalItems: totalItems, + totalSizeMB: totalSize / (1024 * 1024), + )); + } + + Future cacheSearchResults(String query, List results) async { + final key = 'search_$query'; + final data = Uint8List.fromList(utf8.encode(json.encode({ + 'query': query, + 'results': results.map((r) => r.toJson()).toList(), + }))); + + await _storage.write(key, data); + await _prefs.setString('$_metadataPrefix$key', json.encode({ + 'timestamp': DateTime.now().toIso8601String(), + 'type': 'search', + })); + + await _updateStats(); + } + + Future?> getSearchResults(String query) async { + final key = 'search_$query'; + final data = await _storage.read(key); + if (data == null) return null; + + final decoded = json.decode(utf8.decode(data)); + return (decoded['results'] as List) + .map((r) => VideoResult.fromJson(r as Map)) + .toList(); + } + + Future cacheVideoData(String videoId, String videoData) async { + final key = '$_videoPrefix$videoId'; + final data = _extractVideoData(videoData); + + await _storage.write(key, data); + await _prefs.setString('$_metadataPrefix$key', json.encode({ + 'timestamp': DateTime.now().toIso8601String(), + 'type': 'video', + })); + + await _updateStats(); + } + + Future getVideoData(String videoId) async { + final key = '$_videoPrefix$videoId'; + final data = await _storage.read(key); + if (data == null) return null; + + return 'data:video/mp4;base64,${base64Encode(data)}'; + } + + Future cacheThumbnail(String videoId, String thumbnailData) async { + final key = '$_thumbnailPrefix$videoId'; + final data = _extractImageData(thumbnailData); + + await _storage.write(key, data); + await _prefs.setString('$_metadataPrefix$key', json.encode({ + 'timestamp': DateTime.now().toIso8601String(), + 'type': 'thumbnail', + })); + + await _updateStats(); + } + + Future getThumbnail(String videoId) async { + final key = '$_thumbnailPrefix$videoId'; + final data = await _storage.read(key); + if (data == null) return null; + + return 'data:image/jpeg;base64,${base64Encode(data)}'; + } + + Uint8List _extractVideoData(String videoData) { + final parts = videoData.split(','); + if (parts.length != 2) throw Exception('Invalid video data format'); + return base64Decode(parts[1]); + } + + Uint8List _extractImageData(String imageData) { + final parts = imageData.split(','); + if (parts.length != 2) throw Exception('Invalid image data format'); + return base64Decode(parts[1]); + } + + Future delete(String key) async { + await _storage.delete('$_videoPrefix$key'); + await _prefs.remove('$_metadataPrefix$_videoPrefix$key'); + await _updateStats(); + } + + Future clearCache() async { + await _storage.clear(); + final keys = _prefs.getKeys().where((k) => k.startsWith(_metadataPrefix)); + for (final key in keys) { + await _prefs.remove(key); + } + await _updateStats(); + } + + Future cacheSearchResult(String query, VideoResult result, int searchCount) async { + final key = 'search_${query}_$searchCount'; + final data = Uint8List.fromList(utf8.encode(json.encode({ + 'query': query, + 'searchCount': searchCount, + 'result': result.toJson(), + }))); + + await _storage.write(key, data); + await _prefs.setString('$_metadataPrefix$key', json.encode({ + 'timestamp': DateTime.now().toIso8601String(), + 'type': 'search', + })); + + await _updateStats(); + } + + Future> getCachedSearchResults(String query) async { + final results = []; + final searchKeys = _prefs.getKeys() + .where((k) => k.startsWith('${_metadataPrefix}search_$query')); + + for (final key in searchKeys) { + final data = await _storage.read(key.substring(_metadataPrefix.length)); + if (data != null) { + final decoded = json.decode(utf8.decode(data)); + results.add(VideoResult.fromJson(decoded['result'] as Map)); + } + } + + return results..sort((a, b) => a.createdAt.compareTo(b.createdAt)); + } + + Future getLastSearchCount(String query) async { + final searchKeys = _prefs.getKeys() + .where((k) => k.startsWith('${_metadataPrefix}search_$query')) + .toList(); + + if (searchKeys.isEmpty) return 0; + + int maxCount = -1; + for (final key in searchKeys) { + final match = RegExp(r'search_.*_(\d+)$').firstMatch(key); + if (match != null) { + final count = int.parse(match.group(1)!); + if (count > maxCount) maxCount = count; + } + } + + return maxCount + 1; + } + + void dispose() { + _statsController.close(); + } +} + +class CacheStats { + final int totalItems; + final double totalSizeMB; + + CacheStats({ + required this.totalItems, + required this.totalSizeMB, + }); +} \ No newline at end of file diff --git a/lib/services/chat_service.dart b/lib/services/chat_service.dart new file mode 100644 index 0000000000000000000000000000000000000000..5bc4cdfa8bd9127b7f39b299b60d692b07e639a5 --- /dev/null +++ b/lib/services/chat_service.dart @@ -0,0 +1,156 @@ + +// lib/services/chat_service.dart +import 'dart:async'; +import 'dart:math'; +import 'package:aitube2/services/websocket_api_service.dart'; +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:uuid/uuid.dart'; +import '../models/chat_message.dart'; + +class ChatService { + static final ChatService _instance = ChatService._internal(); + factory ChatService() => _instance; + ChatService._internal(); + + static const _userIdKey = 'chat_user_id'; + static const _usernameKey = 'chat_username'; + static const _userColorKey = 'chat_user_color'; + + final _chatController = StreamController.broadcast(); + Stream get chatStream => _chatController.stream; + + final WebSocketApiService _websocketService = WebSocketApiService(); + String? _userId; + String? _username; + String? _userColor; + String? _currentRoomId; + bool _isInitialized = false; + + Future initialize() async { + if (_isInitialized) return; + + final prefs = await SharedPreferences.getInstance(); + _userId = prefs.getString(_userIdKey); + _username = prefs.getString(_usernameKey); + _userColor = prefs.getString(_userColorKey); + + if (_userId == null) { + _userId = const Uuid().v4(); + _username = 'User${_userId!.substring(0, 4)}'; + _userColor = _generateRandomColor(); + + await prefs.setString(_userIdKey, _userId!); + await prefs.setString(_usernameKey, _username!); + await prefs.setString(_userColorKey, _userColor!); + } + + // Set up message handling before attempting to join + _websocketService.chatStream.listen(_handleChatMessage); + _isInitialized = true; + } + + Future joinRoom(String videoId) async { + if (_currentRoomId == videoId) return; // Already in this room + + try { + // Leave current room if in one + if (_currentRoomId != null) { + await leaveRoom(_currentRoomId!); + } + + // Initialize if needed + if (!_isInitialized) { + await initialize(); + } + + await _websocketService.joinChatRoom(videoId); + _currentRoomId = videoId; + debugPrint('Successfully joined chat room for video: $videoId'); + } catch (e) { + debugPrint('Error joining chat room: $e'); + rethrow; + } + } + + Future leaveRoom(String videoId) async { + if (_currentRoomId == videoId && _websocketService.isConnected) { + await _websocketService.leaveChatRoom(videoId); + _currentRoomId = null; + debugPrint('Left chat room for video: $videoId'); + } + } + + String _generateRandomColor() { + final colors = [ + '#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', + '#FFEEAD', '#D4A5A5', '#9B9B9B', '#A8E6CF' + ]; + return colors[Random().nextInt(colors.length)]; + } + + Future sendMessage(String content, String videoId) async { + if (content.trim().isEmpty) return false; + if (content.length > 256) { + content = content.substring(0, 256); + } + + try { + debugPrint('ChatService: Attempting to send message to room $videoId'); + + if (_currentRoomId != videoId) { + debugPrint('ChatService: Not in correct room, joining...'); + await joinRoom(videoId); + } + + if (!_websocketService.isConnected) { + debugPrint('ChatService: WebSocket not connected, attempting to connect...'); + await _websocketService.connect(); + } + + final message = ChatMessage( + userId: _userId!, + username: _username!, + content: content, + videoId: videoId, + color: _userColor, + ); + + debugPrint('ChatService: Sending message via WebSocket...'); + await _websocketService.sendChatMessage(message); + debugPrint('ChatService: Message sent successfully'); + return true; + + } catch (e) { + debugPrint('ChatService: Error sending message: $e'); + if (e is TimeoutException) { + // Try to reconnect on timeout + debugPrint('ChatService: Timeout occurred, attempting to reconnect...'); + try { + await _websocketService.connect(); + debugPrint('ChatService: Reconnected, retrying message send...'); + return sendMessage(content, videoId); // Retry once + } catch (reconnectError) { + debugPrint('ChatService: Reconnection failed: $reconnectError'); + } + } + return false; + } + } + + void _handleChatMessage(ChatMessage message) { + // Only add messages if they're for the current room + if (message.videoId == _currentRoomId) { + _chatController.add(message); + debugPrint('Received chat message: ${message.id} from ${message.username}'); + } + } + + void dispose() { + if (_currentRoomId != null) { + leaveRoom(_currentRoomId!); + } + _chatController.close(); + _isInitialized = false; + } +} diff --git a/lib/services/clip_queue_manager.dart b/lib/services/clip_queue_manager.dart new file mode 100644 index 0000000000000000000000000000000000000000..a91c0c3f1f9005da004fa336bcf95b2e466b867f --- /dev/null +++ b/lib/services/clip_queue_manager.dart @@ -0,0 +1,719 @@ +// lib/services/clip_queue_manager.dart + +import 'dart:async'; +import 'package:aitube2/config/config.dart'; +import 'package:flutter/foundation.dart'; +import 'package:collection/collection.dart'; +import 'package:uuid/uuid.dart'; +import '../models/video_result.dart'; +import '../services/websocket_api_service.dart'; +import '../services/cache_service.dart'; +import '../utils/seed.dart'; + +enum ClipState { + generationPending, + generationInProgress, + generatedAndReadyToPlay, + generatedAndPlaying, + failedToGenerate, + generatedAndPlayed +} + +class VideoClip { + final String id; + final String prompt; + final int seed; + ClipState state; + String? base64Data; + Timer? retryTimer; + Completer? generationCompleter; + DateTime? generationStartTime; + DateTime? generationEndTime; + DateTime? playStartTime; + int retryCount = 0; + static const maxRetries = 3; + + VideoClip({ + String? id, + required this.prompt, + required this.seed, + this.state = ClipState.generationPending, + this.base64Data, + }): id = id ?? const Uuid().v4(); + + bool get isReady => state == ClipState.generatedAndReadyToPlay; + bool get isPending => state == ClipState.generationPending; + bool get isGenerating => state == ClipState.generationInProgress; + bool get isPlaying => state == ClipState.generatedAndPlaying; + bool get hasFailed => state == ClipState.failedToGenerate; + bool get hasPlayed => state == ClipState.generatedAndPlayed; + bool get canRetry => retryCount < maxRetries; + + Duration? get generationDuration { + if (generationStartTime == null) return null; + if (isGenerating) { + return DateTime.now().difference(generationStartTime!); + } + if (isReady || isPlaying || hasPlayed) { + return generationEndTime?.difference(generationStartTime!); + } + return null; + } + + Duration? get playbackDuration { + if (playStartTime == null) return null; + return DateTime.now().difference(playStartTime!); + } + + void startPlaying() { + if (state == ClipState.generatedAndReadyToPlay) { + state = ClipState.generatedAndPlaying; + playStartTime = DateTime.now(); + } + } + + void finishPlaying() { + if (state == ClipState.generatedAndPlaying) { + state = ClipState.generatedAndPlayed; + } + } + + void completeGeneration() { + if (state == ClipState.generationInProgress) { + generationEndTime = DateTime.now(); + state = ClipState.generatedAndReadyToPlay; + } + } + + @override + String toString() => 'VideoClip(seed: $seed, state: $state, retryCount: $retryCount)'; +} + +class ClipQueueManager { + static const bool _showLogsInDebugMode = false; + static const Duration retryDelay = Duration(seconds: 2); + static const Duration clipTimeout = Duration(seconds: 90); + static const Duration generationTimeout = Duration(seconds: 60); + + final VideoResult video; + final WebSocketApiService _websocketService; + final CacheService _cacheService; + final void Function()? onQueueUpdated; + + final List _clipBuffer = []; + final List _clipHistory = []; + final _activeGenerations = {}; + Timer? _bufferCheckTimer; + bool _isDisposed = false; + + DateTime? _lastSuccessfulGeneration; + final _generationTimes = []; + static const _maxStoredGenerationTimes = 10; + + final String videoId; + + DateTime? _lastStateLogTime; + Map? _lastLoggedState; + + ClipQueueManager({ + required this.video, + WebSocketApiService? websocketService, + CacheService? cacheService, + this.onQueueUpdated, + }) : videoId = video.id, + _websocketService = websocketService ?? WebSocketApiService(), + _cacheService = cacheService ?? CacheService(); + + bool get canStartNewGeneration => + _activeGenerations.length < Configuration.instance.renderQueueMaxConcurrentGenerations; + int get pendingGenerations => _clipBuffer.where((c) => c.isPending).length; + int get activeGenerations => _activeGenerations.length; + VideoClip? get currentClip => _clipBuffer.firstWhereOrNull((c) => c.isReady || c.isPlaying); + VideoClip? get nextReadyClip => _clipBuffer.where((c) => c.isReady && !c.isPlaying).firstOrNull; + bool get hasReadyClips => _clipBuffer.any((c) => c.isReady); + List get clipBuffer => List.unmodifiable(_clipBuffer); + List get clipHistory => List.unmodifiable(_clipHistory); + + Future initialize() async { + if (_isDisposed) return; + + _logStateChange('initialize:start'); + _clipBuffer.clear(); + + try { + final bufferSize = Configuration.instance.renderQueueBufferSize; + while (_clipBuffer.length < bufferSize) { + if (_isDisposed) return; + + final newClip = VideoClip( + prompt: "${video.title}\n${video.description}", + seed: video.useFixedSeed && video.seed > 0 ? video.seed : generateSeed(), + ); + _clipBuffer.add(newClip); + _logEvent('Added initial clip ${newClip.seed} to buffer'); + } + + if (_isDisposed) return; + + _startBufferCheck(); + await _fillBuffer(); + _logEvent('Initialization complete. Buffer size: ${_clipBuffer.length}'); + printQueueState(); + } catch (e) { + _logEvent('Initialization error: $e'); + rethrow; + } + + _logStateChange('initialize:complete'); + } + + void _startBufferCheck() { + _bufferCheckTimer?.cancel(); + _bufferCheckTimer = Timer.periodic( + const Duration(milliseconds: 200), + (timer) { + if (!_isDisposed) { + _fillBuffer(); + } + }, + ); + _logEvent('Started buffer check timer'); + } + + void markClipAsPlayed(String clipId) { + _logStateChange('markAsPlayed:start'); + final playingClip = _clipBuffer.firstWhereOrNull((c) => c.id == clipId); + if (playingClip != null) { + playingClip.finishPlaying(); + + final cacheKey = "${video.id}_${playingClip.seed}"; + unawaited(_cacheService.delete(cacheKey).catchError((e) { + debugPrint('Failed to remove clip ${playingClip.seed} from cache: $e'); + })); + + _reorderBufferByPriority(); + _fillBuffer(); + onQueueUpdated?.call(); + } + _logStateChange('markAsPlayed:complete'); + } + + Future _fillBuffer() async { + if (_isDisposed) return; + + // First ensure we have the correct buffer size + while (_clipBuffer.length < Configuration.instance.renderQueueBufferSize) { + final newClip = VideoClip( + prompt: "${video.title}\n${video.description}", + seed: video.useFixedSeed && video.seed > 0 ? video.seed : generateSeed(), + ); + _clipBuffer.add(newClip); + _logEvent('Added new clip ${newClip.seed} to maintain buffer size'); + } + + // Process played clips first + final playedClips = _clipBuffer.where((clip) => clip.hasPlayed).toList(); + if (playedClips.isNotEmpty) { + _processPlayedClips(playedClips); + } + + // Remove failed clips and replace them + final failedClips = _clipBuffer.where((clip) => clip.hasFailed && !clip.canRetry).toList(); + for (final clip in failedClips) { + _clipBuffer.remove(clip); + final newClip = VideoClip( + prompt: "${video.title}\n${video.description}", + seed: video.useFixedSeed && video.seed > 0 ? video.seed : generateSeed(), + ); + _clipBuffer.add(newClip); + } + + // Clean up stuck generations + _checkForStuckGenerations(); + + // Get pending clips that aren't being generated + final pendingClips = _clipBuffer + .where((clip) => clip.isPending && !_activeGenerations.contains(clip.seed.toString())) + .toList(); + + // Calculate available generation slots + final availableSlots = Configuration.instance.renderQueueMaxConcurrentGenerations - _activeGenerations.length; + + if (availableSlots > 0 && pendingClips.isNotEmpty) { + final clipsToGenerate = pendingClips.take(availableSlots).toList(); + _logEvent('Starting ${clipsToGenerate.length} parallel generations'); + + final generationFutures = clipsToGenerate.map((clip) => + _generateClip(clip).catchError((e) { + debugPrint('Generation failed for clip ${clip.seed}: $e'); + return null; + }) + ).toList(); + + unawaited( + Future.wait(generationFutures, eagerError: false).then((_) { + if (!_isDisposed) { + onQueueUpdated?.call(); + // Recursively ensure buffer stays full + _fillBuffer(); + } + }) + ); + } + + onQueueUpdated?.call(); + + _logStateChange('fillBuffer:complete'); + } + + void _reorderBufferByPriority() { + // First, extract all clips that aren't played + final activeClips = _clipBuffer.where((c) => !c.hasPlayed).toList(); + + // Sort clips by priority: + // 1. Currently playing clips stay at their position + // 2. Ready clips move to the front (right after playing clips) + // 3. In-progress generations + // 4. Pending generations + // 5. Failed generations + activeClips.sort((a, b) { + // Helper function to get priority value for a state + int getPriority(ClipState state) { + switch (state) { + case ClipState.generatedAndPlaying: + return 0; + case ClipState.generatedAndReadyToPlay: + return 1; + case ClipState.generationInProgress: + return 2; + case ClipState.generationPending: + return 3; + case ClipState.failedToGenerate: + return 4; + case ClipState.generatedAndPlayed: + return 5; + } + } + + // Compare priorities + final priorityA = getPriority(a.state); + final priorityB = getPriority(b.state); + + if (priorityA != priorityB) { + return priorityA.compareTo(priorityB); + } + + // If same priority, maintain relative order by keeping original indices + return _clipBuffer.indexOf(a).compareTo(_clipBuffer.indexOf(b)); + }); + + // Clear and refill the buffer with the sorted clips + _clipBuffer.clear(); + _clipBuffer.addAll(activeClips); + } + + void _processPlayedClips(List playedClips) { + for (final clip in playedClips) { + _clipBuffer.remove(clip); + _clipHistory.add(clip); + + // Add a new pending clip + final newClip = VideoClip( + prompt: "${video.title}\n${video.description}", + seed: video.useFixedSeed && video.seed > 0 ? video.seed : generateSeed(), + ); + _clipBuffer.add(newClip); + _logEvent('Replaced played clip ${clip.seed} with new clip ${newClip.seed}'); + } + + // Immediately trigger buffer fill to start generating new clips + _fillBuffer(); + } + + void _checkForStuckGenerations() { + final now = DateTime.now(); + var hadStuckGenerations = false; + + for (final clip in _clipBuffer) { + if (clip.isGenerating && + clip.generationStartTime != null && + now.difference(clip.generationStartTime!) > clipTimeout) { + hadStuckGenerations = true; + _handleStuckGeneration(clip); + } + } + + if (hadStuckGenerations) { + _logEvent('Cleaned up stuck generations. Active: ${_activeGenerations.length}'); + } + } + + void _handleStuckGeneration(VideoClip clip) { + _logEvent('Found stuck generation for clip ${clip.seed}'); + + if (_activeGenerations.contains(clip.seed.toString())) { + _activeGenerations.remove(clip.seed.toString()); + } + + clip.state = ClipState.failedToGenerate; + + if (clip.canRetry) { + _scheduleRetry(clip); + } + } + + // Also reorder after retries + void _scheduleRetry(VideoClip clip) { + clip.retryTimer?.cancel(); + clip.retryTimer = Timer(retryDelay, () { + if (!_isDisposed && clip.hasFailed) { + _logEvent('Retrying clip ${clip.seed} (attempt ${clip.retryCount + 1}/${VideoClip.maxRetries})'); + clip.state = ClipState.generationPending; + clip.generationCompleter = null; + clip.generationStartTime = null; + _reorderBufferByPriority(); // Add reordering here + onQueueUpdated?.call(); + _fillBuffer(); + } + }); + } + + Future _generateClip(VideoClip clip) async { + if (clip.isGenerating || clip.isReady || _isDisposed || !canStartNewGeneration) { + return; + } + + final clipSeed = clip.seed.toString(); + if (_activeGenerations.contains(clipSeed)) { + _logEvent('Clip $clipSeed already generating'); + return; + } + + _activeGenerations.add(clipSeed); + clip.state = ClipState.generationInProgress; + clip.generationCompleter = Completer(); + clip.generationStartTime = DateTime.now(); + + try { + final cacheKey = "${video.id}_${clip.seed}"; + String? videoData; + + // Check if we're disposed before proceeding + if (_isDisposed) { + _logEvent('Cancelled generation of clip $clipSeed - manager disposed'); + return; + } + + // Try cache first + try { + videoData = await _cacheService.getVideoData(cacheKey); + } catch (e) { + if (_isDisposed) return; // Check disposed state after each await + debugPrint('Cache error for clip ${clip.seed}: $e'); + } + + if (videoData != null && !_isDisposed) { + await _handleSuccessfulGeneration(clip, videoData, cacheKey); + return; + } + + if (_isDisposed) { + _logEvent('Cancelled generation of clip $clipSeed - manager disposed after cache check'); + return; + } + + // Generate new video with timeout + videoData = await _websocketService.generateVideo( + video, + seed: clip.seed, + ).timeout(generationTimeout); + + if (!_isDisposed) { + await _handleSuccessfulGeneration(clip, videoData, cacheKey); + } + + } catch (e) { + if (!_isDisposed) { + _handleFailedGeneration(clip, e); + } + } finally { + _cleanupGeneration(clip); + } + } + + Future _handleSuccessfulGeneration( + VideoClip clip, + String videoData, + String cacheKey, + ) async { + if (_isDisposed) return; + + clip.base64Data = videoData; + clip.completeGeneration(); + + // Only complete the completer if it exists and isn't already completed + if (clip.generationCompleter != null && !clip.generationCompleter!.isCompleted) { + clip.generationCompleter!.complete(); + } + + // Cache only if the clip isn't already played + if (!clip.hasPlayed) { + unawaited(_cacheService.cacheVideoData(cacheKey, videoData).catchError((e) { + debugPrint('Failed to cache clip ${clip.seed}: $e'); + })); + } + + // Reorder the buffer to prioritize this newly ready clip + _reorderBufferByPriority(); + + _updateGenerationStats(clip); + onQueueUpdated?.call(); + } + + + void _handleFailedGeneration(VideoClip clip, dynamic error) { + if (_isDisposed) return; + _logStateChange('generation:failed:start'); + clip.state = ClipState.failedToGenerate; + clip.retryCount++; + + // Only complete with error if the completer exists and isn't completed + if (clip.generationCompleter != null && !clip.generationCompleter!.isCompleted) { + clip.generationCompleter!.completeError(error); + } + + if (clip.canRetry) { + _scheduleRetry(clip); + } + _logStateChange('generation:failed:complete'); + } + + void _cleanupGeneration(VideoClip clip) { + if (!_isDisposed) { + _activeGenerations.remove(clip.seed.toString()); + onQueueUpdated?.call(); + _fillBuffer(); + } + } + + void _updateGenerationStats(VideoClip clip) { + if (clip.generationStartTime != null) { + final duration = DateTime.now().difference(clip.generationStartTime!); + _generationTimes.add(duration); + if (_generationTimes.length > _maxStoredGenerationTimes) { + _generationTimes.removeAt(0); + } + _lastSuccessfulGeneration = DateTime.now(); + } + } + + Duration? _getAverageGenerationTime() { + if (_generationTimes.isEmpty) return null; + final totalMs = _generationTimes.fold( + 0, + (sum, duration) => sum + duration.inMilliseconds + ); + return Duration(milliseconds: totalMs ~/ _generationTimes.length); + } + + void markCurrentClipAsPlayed() { + _logStateChange('markAsPlayed:start'); + final playingClip = _clipBuffer.firstWhereOrNull((c) => c.isPlaying); + if (playingClip != null) { + playingClip.finishPlaying(); + + // Remove from cache when played + final cacheKey = "${video.id}_${playingClip.seed}"; + unawaited(_cacheService.delete(cacheKey).catchError((e) { + debugPrint('Failed to remove clip ${playingClip.seed} from cache: $e'); + })); + + _reorderBufferByPriority(); + _fillBuffer(); + onQueueUpdated?.call(); + } + _logStateChange('markAsPlayed:complete'); + } + + void startPlayingClip(VideoClip clip) { + _logStateChange('startPlaying:start'); + if (clip.isReady) { + clip.startPlaying(); + onQueueUpdated?.call(); + } + _logStateChange('startPlaying:complete'); + } + + void fillBuffer() { + _logEvent('Manual buffer fill requested'); + _fillBuffer(); + } + + void printQueueState() { + final ready = _clipBuffer.where((c) => c.isReady).length; + final playing = _clipBuffer.where((c) => c.isPlaying).length; + final generating = _activeGenerations.length; + final pending = pendingGenerations; + final failed = _clipBuffer.where((c) => c.hasFailed).length; + + _logEvent('\nQueue State:'); + _logEvent('Buffer size: ${_clipBuffer.length}'); + _logEvent('Ready: $ready, Playing: $playing, Generating: $generating, Pending: $pending, Failed: $failed'); + _logEvent('History size: ${_clipHistory.length}'); + + for (var i = 0; i < _clipBuffer.length; i++) { + final clip = _clipBuffer[i]; + final genDuration = clip.generationDuration; + final playDuration = clip.playbackDuration; + _logEvent('Clip $i: seed=${clip.seed}, state=${clip.state}, ' + 'retries=${clip.retryCount}, generation time=${genDuration?.inSeconds}s' + '${playDuration != null ? ", playing for ${playDuration.inSeconds}s" : ""}'); + } + } + + Map getBufferStats() { + final averageGeneration = _getAverageGenerationTime(); + return { + 'bufferSize': _clipBuffer.length, + 'historySize': _clipHistory.length, + 'activeGenerations': _activeGenerations.length, + 'pendingClips': pendingGenerations, + 'readyClips': _clipBuffer.where((c) => c.isReady).length, + 'failedClips': _clipBuffer.where((c) => c.hasFailed).length, + 'lastSuccessfulGeneration': _lastSuccessfulGeneration?.toString(), + 'averageGenerationTime': averageGeneration?.toString(), + 'clipStates': _clipBuffer.map((c) => c.state.toString()).toList(), + }; + } + + void _logEvent(String message) { + if (_showLogsInDebugMode && kDebugMode) { + debugPrint('ClipQueue: $message'); + } + } + + void _logGenerationStatus() { + final pending = _clipBuffer.where((c) => c.isPending).length; + final generating = _activeGenerations.length; + final ready = _clipBuffer.where((c) => c.isReady).length; + final playing = _clipBuffer.where((c) => c.isPlaying).length; + + _logEvent(''' + Buffer Status: + - Pending: $pending + - Generating: $generating + - Ready: $ready + - Playing: $playing + - Active generations: ${_activeGenerations.join(', ')} + '''); + } + + void _logStateChange(String trigger) { + if (_isDisposed) return; + + final currentState = { + 'readyClips': _clipBuffer.where((c) => c.isReady).length, + 'playingClips': _clipBuffer.where((c) => c.isPlaying).length, + 'generatingClips': _activeGenerations.length, + 'pendingClips': pendingGenerations, + 'failedClips': _clipBuffer.where((c) => c.hasFailed).length, + 'clipStates': _clipBuffer.map((c) => { + 'seed': c.seed, + 'state': c.state.toString(), + 'retryCount': c.retryCount, + 'genDuration': c.generationDuration?.inSeconds, + 'playDuration': c.playbackDuration?.inSeconds, + }).toList(), + 'activeGenerations': List.from(_activeGenerations), + 'historySize': _clipHistory.length, + }; + + // Only log if state has changed + if (_lastLoggedState == null || + !_areStatesEqual(_lastLoggedState!, currentState) || + _shouldLogDueToTimeout()) { + + debugPrint('\n=== Queue State Change [$trigger] ==='); + debugPrint('Ready: ${currentState['readyClips']}'); + debugPrint('Playing: ${currentState['playingClips']}'); + debugPrint('Generating: ${currentState['generatingClips']}'); + + /* + debugPrint('Pending: ${currentState['pendingClips']}'); + debugPrint('Failed: ${currentState['failedClips']}'); + debugPrint('History: ${currentState['historySize']}'); + + debugPrint('\nClip Details:'); + final clipStates = currentState['clipStates'] as List>; + for (var clipState in clipStates) { + debugPrint('Clip ${clipState['seed']}: ${clipState['state']} ' + '(retries: ${clipState['retryCount']}, ' + 'gen: ${clipState['genDuration']}s, ' + 'play: ${clipState['playDuration']}s)'); + } + + final activeGenerations = currentState['activeGenerations'] as List; + if (activeGenerations.isNotEmpty) { + debugPrint('\nActive Generations: ${activeGenerations.join(', ')}'); + } + + debugPrint('=====================================\n'); + + */ + + _lastLoggedState = currentState; + _lastStateLogTime = DateTime.now(); + } + } + + bool _areStatesEqual(Map state1, Map state2) { + return state1['readyClips'] == state2['readyClips'] && + state1['playingClips'] == state2['playingClips'] && + state1['generatingClips'] == state2['generatingClips'] && + state1['pendingClips'] == state2['pendingClips'] && + state1['failedClips'] == state2['failedClips'] && + state1['historySize'] == state2['historySize'] && + const ListEquality().equals( + state1['activeGenerations'] as List, + state2['activeGenerations'] as List + ); + } + + bool _shouldLogDueToTimeout() { + if (_lastStateLogTime == null) return true; + // Force log every 30 seconds even if state hasn't changed + return DateTime.now().difference(_lastStateLogTime!) > const Duration(seconds: 30); + } + + Future dispose() async { + debugPrint('ClipQueueManager: Starting disposal for video $videoId'); + _isDisposed = true; + + // Cancel all timers first + _bufferCheckTimer?.cancel(); + + // Complete any pending generation completers + for (var clip in _clipBuffer) { + clip.retryTimer?.cancel(); + + if (clip.isGenerating && + clip.generationCompleter != null && + !clip.generationCompleter!.isCompleted) { + // Don't throw an error, just complete normally + clip.generationCompleter!.complete(); + } + } + + // Cancel any pending requests for this video + if (videoId.isNotEmpty) { + _websocketService.cancelRequestsForVideo(videoId); + } + + // Clear all collections + _clipBuffer.clear(); + _clipHistory.clear(); + _activeGenerations.clear(); + + debugPrint('ClipQueueManager: Completed disposal for video $videoId'); + } +} \ No newline at end of file diff --git a/lib/services/null_path_provider.dart b/lib/services/null_path_provider.dart new file mode 100644 index 0000000000000000000000000000000000000000..4f92f307f5f272ddf35c90ad23e2548eedf5b9ad --- /dev/null +++ b/lib/services/null_path_provider.dart @@ -0,0 +1,3 @@ +// lib/services/null_path_provider.dart +export 'package:path_provider_platform_interface/path_provider_platform_interface.dart' + show getApplicationCacheDirectory; \ No newline at end of file diff --git a/lib/services/settings_service.dart b/lib/services/settings_service.dart new file mode 100644 index 0000000000000000000000000000000000000000..a7fe6156d66737aa12dc19495764658b64645aa7 --- /dev/null +++ b/lib/services/settings_service.dart @@ -0,0 +1,39 @@ +import 'dart:async'; + +import 'package:shared_preferences/shared_preferences.dart'; + +class SettingsService { + static const String _promptPrefixKey = 'video_prompt_prefix'; + static const String _hfApiKeyKey = 'huggingface_api_key'; + static final SettingsService _instance = SettingsService._internal(); + + factory SettingsService() => _instance; + SettingsService._internal(); + + late SharedPreferences _prefs; + final _settingsController = StreamController.broadcast(); + + Stream get settingsStream => _settingsController.stream; + + Future initialize() async { + _prefs = await SharedPreferences.getInstance(); + } + + String get videoPromptPrefix => _prefs.getString(_promptPrefixKey) ?? ''; + + Future setVideoPromptPrefix(String prefix) async { + await _prefs.setString(_promptPrefixKey, prefix); + _settingsController.add(null); + } + + String get huggingfaceApiKey => _prefs.getString(_hfApiKeyKey) ?? ''; + + Future setHuggingfaceApiKey(String apiKey) async { + await _prefs.setString(_hfApiKeyKey, apiKey); + _settingsController.add(null); + } + + void dispose() { + _settingsController.close(); + } +} diff --git a/lib/services/websocket_api_service.dart b/lib/services/websocket_api_service.dart new file mode 100644 index 0000000000000000000000000000000000000000..fdae3ac5853d89495b81c6533923adacdca2b8a3 --- /dev/null +++ b/lib/services/websocket_api_service.dart @@ -0,0 +1,898 @@ +import 'dart:async'; +import 'dart:io'; +import 'package:aitube2/services/settings_service.dart'; +import 'package:synchronized/synchronized.dart'; +import 'dart:convert'; +import 'package:aitube2/config/config.dart'; +import 'package:aitube2/models/chat_message.dart'; +import 'package:flutter/foundation.dart'; +import 'package:uuid/uuid.dart'; +import 'package:web_socket_channel/web_socket_channel.dart'; +import '../models/search_state.dart'; +import '../models/video_result.dart'; + +class WebSocketRequest { + final String requestId; + final String action; + final Map params; + + WebSocketRequest({ + String? requestId, + required this.action, + required this.params, + }) : requestId = requestId ?? const Uuid().v4(); + + Map toJson() => { + 'requestId': requestId, + 'action': action, + ...params, + }; +} + +enum ConnectionStatus { + disconnected, + connecting, + connected, + reconnecting, + error, + maintenance +} + +class WebSocketApiService { + // Singleton implementation + static final WebSocketApiService _instance = WebSocketApiService._internal(); + factory WebSocketApiService() => _instance; + WebSocketApiService._internal(); + + static const String _wsUrl = 'ws://localhost:8080/ws'; + WebSocketChannel? _channel; + final _responseController = StreamController>.broadcast(); + final _pendingRequests = >>{}; + final _statusController = StreamController.broadcast(); + Timer? _heartbeatTimer; + Timer? _reconnectTimer; + bool _disposed = false; + int _reconnectAttempts = 0; + static const int _maxReconnectAttempts = 5; + static const Duration _initialReconnectDelay = Duration(seconds: 2); + static bool _initialized = false; + + final _connectionLock = Lock(); + final _disposeLock = Lock(); + final bool _isReconnecting = false; + + final _chatController = StreamController.broadcast(); + Stream get chatStream => _chatController.stream; + + Stream get statusStream => _statusController.stream; + ConnectionStatus _status = ConnectionStatus.disconnected; + ConnectionStatus get status => _status; + bool get isConnected => _status == ConnectionStatus.connected; + bool get isInMaintenance => _status == ConnectionStatus.maintenance; + + SearchState? _currentSearchState; + final _searchController = StreamController.broadcast(); + final _activeSearches = {}; + static const int maxFailedAttempts = 3; + static const int maxResults = 4; + + Stream get searchStream => _searchController.stream; + + static const Duration _minRequestInterval = Duration(milliseconds: 100); + DateTime _lastRequestTime = DateTime.now(); + final _activeRequests = {}; + + final _subscribers = {}; + + // Track the user role + String _userRole = 'anon'; + String get userRole => _userRole; + + // Stream to notify listeners when user role changes + final _userRoleController = StreamController.broadcast(); + Stream get userRoleStream => _userRoleController.stream; + + Future initialize() async { + if (_initialized) return; + + try { + await connect(); + // Request user role after connection + await _requestUserRole(); + _initialized = true; + } catch (e) { + debugPrint('Failed to initialize WebSocketApiService: $e'); + rethrow; + } + } + + Future _requestUserRole() async { + try { + final response = await _sendRequest( + WebSocketRequest( + action: 'get_user_role', + params: {}, + ), + timeout: const Duration(seconds: 5), + ); + + if (response['success'] == true && response['user_role'] != null) { + _userRole = response['user_role'] as String; + _userRoleController.add(_userRole); + debugPrint('WebSocketApiService: User role set to $_userRole'); + } + } catch (e) { + debugPrint('WebSocketApiService: Failed to get user role: $e'); + } + } + + Future connect() async { + if (_disposed) { + throw Exception('WebSocketApiService has been disposed'); + } + + // Prevent multiple simultaneous connection attempts + return _connectionLock.synchronized(() async { + if (_status == ConnectionStatus.connecting || + _status == ConnectionStatus.connected) { + return; + } + + try { + _setStatus(ConnectionStatus.connecting); + + // Close existing channel if any + await _channel?.sink.close(); + _channel = null; + + // Get the HF API key if available + final settings = SettingsService(); + final hfApiKey = settings.huggingfaceApiKey; + + // Construct the connection URL with the API key as a query parameter if available + final baseUrl = Uri.parse(_wsUrl); + final connectionUrl = hfApiKey.isNotEmpty + ? baseUrl.replace(queryParameters: {'hf_token': hfApiKey}) + : baseUrl; + + debugPrint('WebSocketApiService: Connecting to WebSocket with API key: ${hfApiKey.isNotEmpty ? 'provided' : 'not provided'}'); + + // First check if server is in maintenance mode by making an HTTP request to the status endpoint + try { + final httpUrl = 'http://${baseUrl.authority}/api/status'; + final httpClient = HttpClient(); + final request = await httpClient.getUrl(Uri.parse(httpUrl)); + final response = await request.close(); + + if (response.statusCode == 200) { + final responseBody = await response.transform(utf8.decoder).join(); + final statusData = jsonDecode(responseBody); + + if (statusData['maintenance_mode'] == true) { + debugPrint('WebSocketApiService: Server is in maintenance mode'); + _setStatus(ConnectionStatus.maintenance); + return; + } + } + } catch (e) { + debugPrint('WebSocketApiService: Failed to check maintenance status: $e'); + // Continue with connection attempt even if status check fails + } + + try { + _channel = WebSocketChannel.connect(connectionUrl); + } catch (e) { + debugPrint('WebSocketApiService: Failed to create WebSocket channel: $e'); + + // If connection fails and we were using an API key, try without it + if (hfApiKey.isNotEmpty) { + debugPrint('WebSocketApiService: Retrying connection without API key'); + _channel = WebSocketChannel.connect(baseUrl); + } else { + rethrow; + } + } + + // Wait for connection with proper error handling + try { + await _channel!.ready.timeout( + const Duration(seconds: 10), + onTimeout: () { + _setStatus(ConnectionStatus.error); + throw TimeoutException('Connection timeout'); + }, + ); + } catch (e) { + debugPrint('WebSocketApiService: Connection failed: $e'); + + // If server sent a 503 response with maintenance mode indication + if (e.toString().contains('503') && e.toString().contains('maintenance')) { + debugPrint('WebSocketApiService: Server is in maintenance mode'); + _setStatus(ConnectionStatus.maintenance); + return; + } + + // If connection fails and we were using an API key, try without it + if (hfApiKey.isNotEmpty) { + debugPrint('WebSocketApiService: Retrying connection without API key after ready timeout'); + + // Close the failed channel + await _channel?.sink.close(); + + // Try connecting without the API key + _channel = WebSocketChannel.connect(baseUrl); + + try { + await _channel!.ready.timeout( + const Duration(seconds: 10), + onTimeout: () { + _setStatus(ConnectionStatus.error); + throw TimeoutException('Connection timeout on fallback attempt'); + }, + ); + } catch (retryError) { + // Check again for maintenance mode + if (retryError.toString().contains('503') && retryError.toString().contains('maintenance')) { + debugPrint('WebSocketApiService: Server is in maintenance mode'); + _setStatus(ConnectionStatus.maintenance); + return; + } + + debugPrint('WebSocketApiService: Fallback connection also failed: $retryError'); + rethrow; + } + } else { + rethrow; + } + } + + // Setup stream listener with error handling + _channel!.stream.listen( + _handleMessage, + onError: _handleError, + onDone: _handleDisconnect, + cancelOnError: true, + ); + + _startHeartbeat(); + _setStatus(ConnectionStatus.connected); + _reconnectAttempts = 0; + } catch (e) { + // Check if the error indicates maintenance mode + if (e.toString().contains('maintenance')) { + debugPrint('WebSocketApiService: Server is in maintenance mode'); + _setStatus(ConnectionStatus.maintenance); + } else { + debugPrint('WebSocketApiService: Connection error: $e'); + _setStatus(ConnectionStatus.error); + rethrow; + } + } + }); + } + + void addSubscriber(String id) { + _subscribers[id] = (_subscribers[id] ?? 0) + 1; + debugPrint('WebSocket subscriber added: $id (total: ${_subscribers[id]})'); + } + + void removeSubscriber(String id) { + if (_subscribers.containsKey(id)) { + _subscribers[id] = _subscribers[id]! - 1; + if (_subscribers[id]! <= 0) { + _subscribers.remove(id); + } + debugPrint('WebSocket subscriber removed: $id (remaining: ${_subscribers[id] ?? 0})'); + } + } + + Future joinChatRoom(String videoId) async { + debugPrint('WebSocketApiService: Attempting to join chat room: $videoId'); + + if (!isConnected) { + debugPrint('WebSocketApiService: Not connected, connecting first...'); + await connect(); + } + + try { + final response = await _sendRequest( + WebSocketRequest( + action: 'join_chat', + params: {'videoId': videoId}, + ), + timeout: const Duration(seconds: 10), + ); + + debugPrint('WebSocketApiService: Join chat room response received: $response'); + + if (!response['success']) { + final error = response['error'] ?? 'Failed to join chat room'; + debugPrint('WebSocketApiService: Join chat room failed: $error'); + throw Exception(error); + } + + // Process chat history if provided + if (response['messages'] != null) { + _handleChatHistory(response); + } + + debugPrint('WebSocketApiService: Successfully joined chat room: $videoId'); + } catch (e) { + debugPrint('WebSocketApiService: Error joining chat room: $e'); + rethrow; + } + } + + + Future leaveChatRoom(String videoId) async { + if (!isConnected) return; + + try { + await _sendRequest( + WebSocketRequest( + action: 'leave_chat', + params: {'videoId': videoId}, + ), + timeout: const Duration(seconds: 5), + ); + debugPrint('Successfully left chat room: $videoId'); + } catch (e) { + debugPrint('Failed to leave chat room: $e'); + } + } + + ////// ---- OLD VERSION OF THE CODE ------ + /// + + + Future startContinuousSearch(String query) async { + if (!_initialized) { + await initialize(); + } + + debugPrint('Starting continuous search for query: $query'); + _activeSearches[query] = true; + _currentSearchState = SearchState(query: query); + int failedAttempts = 0; + + while (_activeSearches[query] == true && + !_disposed && + failedAttempts < maxFailedAttempts && + (_currentSearchState?.resultCount ?? 0) < maxResults) { + try { + final response = await _sendRequest( + WebSocketRequest( + action: 'search', + params: { + 'query': query, + 'searchCount': _currentSearchState?.resultCount ?? 0, + 'attemptCount': failedAttempts, + }, + ), + timeout: const Duration(seconds: 30), + ); + + if (_disposed || _activeSearches[query] != true) break; + + if (response['success'] == true && response['result'] != null) { + final result = VideoResult.fromJson(response['result'] as Map); + _searchController.add(result); + _currentSearchState = _currentSearchState?.incrementCount(); + failedAttempts = 0; + } else { + failedAttempts++; + debugPrint('Search attempt $failedAttempts failed for query: $query. Error: ${response['error']}'); + } + } catch (e) { + failedAttempts++; + debugPrint('Search error (attempt $failedAttempts): $e'); + + if (failedAttempts < maxFailedAttempts) { + await Future.delayed(const Duration(seconds: 2)); + } + } + } + + _activeSearches[query] = false; + + if (_disposed) { + debugPrint('Search terminated: Service disposed'); + } else if (failedAttempts >= maxFailedAttempts) { + debugPrint('Search terminated: Max failures ($maxFailedAttempts) reached'); + } else if ((_currentSearchState?.resultCount ?? 0) >= maxResults) { + debugPrint('Search terminated: Max results ($maxResults) reached'); + } else { + debugPrint('Search terminated: Search cancelled'); + } + } + + void stopContinuousSearch(String query) { + _activeSearches[query] = false; + } + + String get statusMessage { + switch (_status) { + case ConnectionStatus.disconnected: + return 'Disconnected '; + case ConnectionStatus.connecting: + return 'Connecting...'; + case ConnectionStatus.connected: + return 'Connected'; + case ConnectionStatus.reconnecting: + return 'Connection lost. Attempting to reconnect (${_reconnectAttempts + 1}/$_maxReconnectAttempts)...'; + case ConnectionStatus.error: + return 'Failed to connect'; + case ConnectionStatus.maintenance: + return 'Server is in maintenance mode'; + } + } + + void _setStatus(ConnectionStatus newStatus) { + if (_status != newStatus) { + _status = newStatus; + _statusController.add(newStatus); + if (kDebugMode) { + print('WebSocket Status: $statusMessage'); + } + } + } + + void _startHeartbeat() { + _heartbeatTimer?.cancel(); + _heartbeatTimer = Timer.periodic(const Duration(seconds: 30), (timer) { + if (isConnected) { + _channel?.sink.add(json.encode({ + 'action': 'heartbeat', + 'requestId': const Uuid().v4(), + })); + } + }); + } + + Future sendChatMessage(ChatMessage message) async { + if (!_initialized) { + debugPrint('WebSocketApiService: Initializing before sending message...'); + await initialize(); + } + + try { + debugPrint('WebSocketApiService: Sending chat message...'); + + final response = await _sendRequest( + WebSocketRequest( + action: 'chat_message', + params: { + 'videoId': message.videoId, + ...message.toJson(), + }, + ), + timeout: const Duration(seconds: 10), + ); + + if (!response['success']) { + debugPrint('WebSocketApiService: Server returned error: ${response['error']}'); + throw Exception(response['error'] ?? 'Failed to send message'); + } + + debugPrint('WebSocketApiService: Message sent successfully'); + return true; + } catch (e) { + debugPrint('WebSocketApiService: Error in sendChatMessage: $e'); + rethrow; + } + } + + void _handleMessage(dynamic message) { + try { + final data = json.decode(message as String) as Map; + final action = data['action'] as String?; + final requestId = data['requestId'] as String?; + + // debugPrint('WebSocketApiService: Received message for action: $action, requestId: $requestId'); + + // Update user role if present in response (from heartbeat or get_user_role) + if (data['user_role'] != null) { + final newRole = data['user_role'] as String; + if (_userRole != newRole) { + _userRole = newRole; + _userRoleController.add(_userRole); + debugPrint('WebSocketApiService: User role updated to $_userRole'); + } + } + + if (requestId != null && _pendingRequests.containsKey(requestId)) { + if (action == 'chat_message') { + debugPrint('WebSocketApiService: Processing chat message response'); + // Extract the message data for chat messages + if (data['success'] == true && data['message'] != null) { + _handleChatMessage(data['message'] as Map); + } + _pendingRequests[requestId]!.complete(data); + } else if (action == 'join_chat') { + debugPrint('WebSocketApiService: Processing join chat response'); + _pendingRequests[requestId]!.complete(data); + } else { + // debugPrint('WebSocketApiService: Processing generic response'); + _pendingRequests[requestId]!.complete(data); + } + + _cleanup(requestId); + } else if (action == 'chat_message' && data['broadcast'] == true) { + // For broadcast messages, the message is directly in the data + debugPrint('WebSocketApiService: Processing chat broadcast'); + _handleChatMessage(data); + } + + } catch (e, stackTrace) { + debugPrint('WebSocketApiService: Error handling message: $e'); + debugPrint('Stack trace: $stackTrace'); + } + } + + void _handleChatMessage(Map data) { + try { + // Log the exact data we're trying to parse + debugPrint('Parsing chat message data: ${json.encode(data)}'); + + // Verify required fields are present + final requiredFields = ['userId', 'username', 'content', 'videoId']; + final missingFields = requiredFields.where((field) => !data.containsKey(field) || data[field] == null); + + if (missingFields.isNotEmpty) { + throw FormatException( + 'Missing required fields: ${missingFields.join(', ')}' + ); + } + + final message = ChatMessage.fromJson(data); + debugPrint('Successfully parsed message: ${message.toString()}'); + _chatController.add(message); + } catch (e, stackTrace) { + debugPrint('Error handling chat message: $e'); + debugPrint('Stack trace: $stackTrace'); + debugPrint('Raw message data: ${json.encode(data)}'); + } + } + + + void _handleChatHistory(Map data) { + try { + if (data['messages'] == null) { + debugPrint('No messages found in chat history'); + return; + } + + final messages = (data['messages'] as List).map((m) { + try { + return ChatMessage.fromJson(m as Map); + } catch (e) { + debugPrint('Error parsing historical message: $e'); + debugPrint('Raw message data: ${json.encode(m)}'); + return null; + } + }).whereType().toList(); + + debugPrint('Processing ${messages.length} historical messages'); + + for (final message in messages) { + _chatController.add(message); + } + } catch (e, stackTrace) { + debugPrint('Error handling chat history: $e'); + debugPrint('Stack trace: $stackTrace'); + } + } + + void _handleError(dynamic error) { + debugPrint('WebSocket error occurred: $error'); + _setStatus(ConnectionStatus.error); + _scheduleReconnect(); + } + + void _handleDisconnect() { + debugPrint('WebSocket disconnected'); + _setStatus(ConnectionStatus.disconnected); + _scheduleReconnect(); + } + + void _scheduleReconnect() { + if (_disposed || isConnected || _status == ConnectionStatus.reconnecting) { + return; + } + + _reconnectTimer?.cancel(); + + if (_reconnectAttempts >= _maxReconnectAttempts) { + _setStatus(ConnectionStatus.error); + _cancelPendingRequests('Max reconnection attempts reached'); + return; + } + + _setStatus(ConnectionStatus.reconnecting); + + final delay = _initialReconnectDelay * (1 << _reconnectAttempts); + _reconnectTimer = Timer(delay, () async { + _reconnectAttempts++; + try { + await connect(); + } catch (e) { + debugPrint('Reconnection attempt failed: $e'); + } + }); + } + + void _cancelPendingRequests([String? error]) { + final err = error ?? 'WebSocket connection closed'; + _pendingRequests.forEach((_, completer) { + if (!completer.isCompleted) { + completer.completeError(err); + } + }); + _pendingRequests.clear(); + } + + Future> _sendRequest(WebSocketRequest request, {Duration? timeout}) async { + // Throttle requests + final now = DateTime.now(); + final timeSinceLastRequest = now.difference(_lastRequestTime); + if (timeSinceLastRequest < _minRequestInterval) { + await Future.delayed(_minRequestInterval - timeSinceLastRequest); + } + _lastRequestTime = DateTime.now(); + + // Prevent duplicate requests + if (_activeRequests[request.requestId] == true) { + debugPrint('WebSocketApiService: Duplicate request detected ${request.requestId}'); + throw Exception('Duplicate request'); + } + _activeRequests[request.requestId] = true; + + if (!isConnected) { + debugPrint('WebSocketApiService: Connecting before sending request...'); + await connect(); + } + + final completer = Completer>(); + _pendingRequests[request.requestId] = completer; + + try { + final requestData = request.toJson(); + // debugPrint('WebSocketApiService: Sending request ${request.requestId} (${request.action}): ${json.encode(requestData)}'); + _channel!.sink.add(json.encode(requestData)); + + final response = await completer.future.timeout( + timeout ?? const Duration(seconds: 10), + onTimeout: () { + debugPrint('WebSocketApiService: Request ${request.requestId} timed out'); + _cleanup(request.requestId); + throw TimeoutException('Request timeout'); + }, + ); + + return response; + } catch (e) { + debugPrint('WebSocketApiService: Error in _sendRequest: $e'); + _cleanup(request.requestId); + rethrow; + } + } + + void _cleanup(String requestId) { + _pendingRequests.remove(requestId); + _activeRequests.remove(requestId); + } + + Future search(String query) async { + if (query.trim().isEmpty) { + throw Exception('Search query cannot be empty'); + } + + try { + final response = await _sendRequest( + WebSocketRequest( + action: 'search', + params: {'query': query}, + ), + timeout: const Duration(seconds: 30), + ); + + if (!response['success']) { + throw Exception(response['error'] ?? 'Search failed'); + } + + final result = response['result']; + if (result == null) { + throw Exception('No result returned from search'); + } + + return VideoResult.fromJson(result as Map); + + } catch (e) { + throw Exception('Error performing search: $e'); + } + } + + Future generateVideo(VideoResult video, { + bool enhancePrompt = false, + String? negativePrompt, + int height = 320, + int width = 512, + int seed = 0, + Duration timeout = const Duration(seconds: 12), // we keep things super tight, as normally a video only takes 2~3s to generate + }) async { + final settings = SettingsService(); + + final response = await _sendRequest( + WebSocketRequest( + action: 'generate_video', + params: { + 'title': video.title, + 'description': video.description, + 'video_prompt_prefix': settings.videoPromptPrefix, + 'options': { + 'enhance_prompt': enhancePrompt, + 'negative_prompt': negativePrompt ?? 'low quality, worst quality, deformed, distorted, disfigured, blurry, text, watermark', + 'frame_rate': Configuration.instance.originalClipFrameRate, + 'num_inference_steps': Configuration.instance.numInferenceSteps, + 'guidance_scale': Configuration.instance.guidanceScale, + 'height': Configuration.instance.originalClipHeight, + 'width': Configuration.instance.originalClipWidth, + 'num_frames': Configuration.instance.originalClipNumberOfFrames, + 'seed': seed, + }, + }, + ), + timeout: timeout, + ); + + if (!response['success']) { + throw Exception(response['error'] ?? 'Video generation failed'); + } + + return response['video'] as String; + } + + Future generateCaption(String title, String description) async { + final response = await _sendRequest( + WebSocketRequest( + action: 'generate_caption', + params: { + 'title': title, + 'description': description, + }, + ), + timeout: const Duration(seconds: 45), + ); + + if (!response['success']) { + throw Exception(response['error'] ?? 'caption generation failed'); + } + + return response['caption'] as String; + } + + Future generateThumbnail(String title, String description) async { + const int maxRetries = 3; + const Duration baseDelay = Duration(seconds: 2); + + for (int attempt = 0; attempt < maxRetries; attempt++) { + try { + debugPrint('Attempting to generate thumbnail (attempt ${attempt + 1}/$maxRetries)'); + + final response = await _sendRequest( + WebSocketRequest( + action: 'generate_thumbnail', + params: { + 'title': title, + 'description': "$description (attempt $attempt)", + 'attempt': attempt, + }, + ), + timeout: const Duration(seconds: 60), + ); + + if (response['success'] == true) { + debugPrint('Successfully generated thumbnail on attempt ${attempt + 1}'); + return response['thumbnailUrl'] as String; + } + + throw Exception(response['error'] ?? 'Thumbnail generation failed'); + + } catch (e) { + debugPrint('Error generating thumbnail (attempt ${attempt + 1}): $e'); + + // If this was our last attempt, rethrow the error + if (attempt == maxRetries - 1) { + throw Exception('Failed to generate thumbnail after $maxRetries attempts: $e'); + } + + // Exponential backoff for retries + final delay = baseDelay * (attempt + 1); + debugPrint('Waiting ${delay.inSeconds}s before retry...'); + await Future.delayed(delay); + } + } + + // This shouldn't be reached due to the throw in the loop, but Dart requires it + throw Exception('Failed to generate thumbnail after $maxRetries attempts'); + } + + // Additional utility methods + Future waitForConnection() async { + if (isConnected) return; + + final completer = Completer(); + StreamSubscription? subscription; + + subscription = statusStream.listen((status) { + if (status == ConnectionStatus.connected) { + subscription?.cancel(); + completer.complete(); + } else if (status == ConnectionStatus.error) { + subscription?.cancel(); + completer.completeError('Failed to connect'); + } + }); + + await connect(); + return completer.future; + } + + void cancelRequestsForVideo(String videoId) { + final requestsToCancel = _pendingRequests.entries + .where((entry) => entry.key.startsWith('video_$videoId')) + .toList(); + + for (var entry in requestsToCancel) { + if (!entry.value.isCompleted) { + entry.value.completeError('Video closed'); + } + _cleanup(entry.key); + } + } + + Future dispose() async { + if (_subscribers.isNotEmpty) { + debugPrint('WebSocketApiService: Skipping disposal - active subscribers remain: ${_subscribers.length}'); + return; + } + + // Use the lock to prevent multiple simultaneous disposal attempts + return _disposeLock.synchronized(() async { + if (_disposed) return; + + debugPrint('WebSocketApiService: Starting disposal...'); + _disposed = true; + _initialized = false; + + // Cancel timers + _heartbeatTimer?.cancel(); + _reconnectTimer?.cancel(); + + // Clear all pending requests + _cancelPendingRequests('Service is being disposed'); + + // Close channel properly + if (_channel != null) { + try { + await _channel!.sink.close(); + } catch (e) { + debugPrint('WebSocketApiService: Error closing channel: $e'); + } + } + + // Close controllers + await _responseController.close(); + await _statusController.close(); + await _searchController.close(); + await _chatController.close(); + await _userRoleController.close(); + + _activeSearches.clear(); + _channel = null; + + debugPrint('WebSocketApiService: Disposal complete'); + }); + } + +} \ No newline at end of file diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart new file mode 100644 index 0000000000000000000000000000000000000000..82b531cfcdba1dd879ca478faff050d7f5d80039 --- /dev/null +++ b/lib/theme/colors.dart @@ -0,0 +1,12 @@ +// lib/theme/Colors.dart +import 'package:flutter/material.dart'; + +class AiTubeColors { + static const background = Color(0xFF0F0F0F); + static const surface = Color(0xFF242424); + static const surfaceVariant = Color(0xFF2D2D2D); + static const primary = Colors.red; + static const onBackground = Colors.white; + static const onSurface = Colors.white; + static const onSurfaceVariant = Colors.white70; +} diff --git a/lib/utils/seed.dart b/lib/utils/seed.dart new file mode 100644 index 0000000000000000000000000000000000000000..6ee8c394548a5c97ffe2fdd3ed4b13b42c408f4d --- /dev/null +++ b/lib/utils/seed.dart @@ -0,0 +1,5 @@ +import 'dart:math'; + +int generateSeed() { + return Random().nextInt(pow(2, 31) as int); +} diff --git a/lib/widgets/api_key_dialog.dart b/lib/widgets/api_key_dialog.dart new file mode 100644 index 0000000000000000000000000000000000000000..d663f80940c782a82aee396f824ffd651d8f5e0b --- /dev/null +++ b/lib/widgets/api_key_dialog.dart @@ -0,0 +1,67 @@ +// lib/widgets/api_key_dialog.dart +import 'package:flutter/material.dart'; +import '../theme/colors.dart'; + +class ApiKeyDialog extends StatefulWidget { + const ApiKeyDialog({super.key}); + + @override + State createState() => _ApiKeyDialogState(); +} + +class _ApiKeyDialogState extends State { + final _controller = TextEditingController(); + bool _obscureText = true; + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: const Text( + 'Enter HuggingFace API Key', + style: TextStyle( + color: AiTubeColors.onBackground, + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + content: TextField( + controller: _controller, + obscureText: _obscureText, + decoration: InputDecoration( + labelText: 'API Key', + labelStyle: const TextStyle(color: AiTubeColors.onSurfaceVariant), + suffixIcon: IconButton( + icon: Icon( + _obscureText ? Icons.visibility : Icons.visibility_off, + color: AiTubeColors.onSurfaceVariant, + ), + onPressed: () => setState(() => _obscureText = !_obscureText), + ), + ), + ), + backgroundColor: AiTubeColors.surface, + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text( + 'Cancel', + style: TextStyle(color: AiTubeColors.onSurfaceVariant), + ), + ), + FilledButton( + onPressed: () => Navigator.pop(context, _controller.text), + style: FilledButton.styleFrom( + backgroundColor: AiTubeColors.primary, + ), + child: const Text('Save'), + ), + ], + ); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } +} \ No newline at end of file diff --git a/lib/widgets/chat_widget.dart b/lib/widgets/chat_widget.dart new file mode 100644 index 0000000000000000000000000000000000000000..b3dc51761bb109d97b390400ad3d8c5026fda2b1 --- /dev/null +++ b/lib/widgets/chat_widget.dart @@ -0,0 +1,414 @@ +// lib/widgets/chat_widget.dart +import 'dart:async'; + +import 'package:flutter/material.dart'; +import '../models/chat_message.dart'; +import '../services/chat_service.dart'; +import '../theme/colors.dart'; + +class ChatWidget extends StatefulWidget { + final String videoId; + final bool isCompact; + + const ChatWidget({ + super.key, + required this.videoId, + this.isCompact = false, + }); + + @override + State createState() => _ChatWidgetState(); +} + +class _ChatWidgetState extends State { + final _chatService = ChatService(); + final _messageController = TextEditingController(); + final _scrollController = ScrollController(); + final _messages = []; + bool _isLoading = true; + bool _isSending = false; + String? _error; + Timer? _reconnectTimer; + bool _disposed = false; + + @override + void initState() { + super.initState(); + _initialize(); + } + + Future _initialize() async { + if (_disposed) return; + + debugPrint('ChatWidget: Starting initialization for video ${widget.videoId}...'); + try { + await _chatService.initialize(); + + int retryCount = 0; + const maxRetries = 3; + + while (retryCount < maxRetries && !_disposed) { + try { + debugPrint('ChatWidget: Attempting to join room ${widget.videoId} (attempt ${retryCount + 1})'); + await _chatService.joinRoom(widget.videoId).timeout( + const Duration(seconds: 10), + onTimeout: () { + debugPrint('ChatWidget: Join room timeout on attempt ${retryCount + 1}'); + throw TimeoutException('Failed to join chat room'); + }, + ); + + if (_disposed) return; + + debugPrint('ChatWidget: Successfully joined room ${widget.videoId}'); + break; + } catch (e) { + retryCount++; + debugPrint('ChatWidget: Attempt $retryCount failed: $e'); + + if (retryCount >= maxRetries || _disposed) { + debugPrint('ChatWidget: Max retries reached or widget disposed, throwing error'); + throw Exception('Failed to join chat room after $maxRetries attempts'); + } + + final delay = Duration(seconds: 1 << retryCount); + debugPrint('ChatWidget: Waiting ${delay.inSeconds}s before retry...'); + await Future.delayed(delay); + } + } + + if (!_disposed) { + _chatService.chatStream.listen( + _onNewMessage, + onError: (error) { + debugPrint('ChatWidget: Chat stream error: $error'); + _handleError(error); + }, + ); + + if (mounted) { + setState(() { + _isLoading = false; + _error = null; + }); + } + } + } catch (e) { + debugPrint('ChatWidget: Initialization error: $e'); + if (mounted && !_disposed) { + setState(() { + _isLoading = false; + _error = 'Failed to connect to chat. Tap to retry.'; + }); + } + } + } + + void _handleError(dynamic error) { + if (_disposed || !mounted) return; + + setState(() { + _error = 'Connection error. Tap to retry.'; + _isLoading = false; + }); + + // Schedule reconnection + _reconnectTimer?.cancel(); + _reconnectTimer = Timer(const Duration(seconds: 5), _initialize); + } + + void _onNewMessage(ChatMessage message) { + if (!mounted) return; + + setState(() { + _messages.add(message); + // Keep only last 100 messages + if (_messages.length > 100) { + _messages.removeAt(0); + } + }); + + // Auto-scroll to bottom with a slight delay + Future.delayed(const Duration(milliseconds: 100), () { + if (_scrollController.hasClients) { + _scrollController.animateTo( + _scrollController.position.maxScrollExtent, + duration: const Duration(milliseconds: 200), + curve: Curves.easeOut, + ); + } + }); + } + + Widget _buildMessageList() { + if (_messages.isEmpty) { + return const Center( + child: Text( + 'No messages yet', + style: TextStyle( + color: AiTubeColors.onSurfaceVariant, + fontSize: 14, + ), + ), + ); + } + + return ListView.builder( + controller: _scrollController, + padding: const EdgeInsets.all(8), + itemCount: _messages.length, + itemBuilder: (context, index) { + final message = _messages[index]; + return _buildMessageTile(message); + }, + ); + } + + Widget _buildMessageTile(ChatMessage message) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CircleAvatar( + radius: 16, + backgroundColor: Color( + int.parse(message.color?.substring(1) ?? 'FF4444', + radix: 16) | 0xFF000000, + ), + child: Text( + message.username.substring(0, 1).toUpperCase(), + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ), + const SizedBox(width: 8), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + message.username, + style: const TextStyle( + color: AiTubeColors.onBackground, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(width: 8), + Text( + _formatTime(message.timestamp), + style: const TextStyle( + color: AiTubeColors.onSurfaceVariant, + fontSize: 12, + ), + ), + ], + ), + const SizedBox(height: 4), + Text( + message.content, + style: const TextStyle(color: AiTubeColors.onSurface), + ), + ], + ), + ), + ], + ), + ); + } + + Widget _buildMessageInput() { + return Container( + padding: const EdgeInsets.all(8), + decoration: const BoxDecoration( + color: AiTubeColors.surface, + border: Border( + top: BorderSide( + color: AiTubeColors.surfaceVariant, + width: 1, + ), + ), + ), + child: Row( + children: [ + Expanded( + child: TextField( + controller: _messageController, + style: const TextStyle(color: AiTubeColors.onSurface), + maxLength: 256, + decoration: InputDecoration( + hintText: 'Type a message...', + hintStyle: const TextStyle(color: AiTubeColors.onSurfaceVariant), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(24), + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + counterText: '', + ), + ), + ), + const SizedBox(width: 8), + IconButton( + icon: _isSending + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator(strokeWidth: 2), + ) + : const Icon(Icons.send), + color: AiTubeColors.primary, + onPressed: _isSending ? null : _sendMessage, + ), + ], + ), + ); + } + + String _formatTime(DateTime time) { + final now = DateTime.now(); + final difference = now.difference(time); + + if (difference.inMinutes < 1) { + return 'just now'; + } else if (difference.inHours < 1) { + return '${difference.inMinutes}m ago'; + } else if (difference.inDays < 1) { + return '${difference.inHours}h ago'; + } else { + return '${difference.inDays}d ago'; + } + } + + Future _sendMessage() async { + final content = _messageController.text.trim(); + if (content.isEmpty) return; + + setState(() => _isSending = true); + + try { + final success = await _chatService.sendMessage(content, widget.videoId); + + if (success) { + _messageController.clear(); + FocusScope.of(context).unfocus(); + } else { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Failed to send message. Please try again.'), + duration: Duration(seconds: 2), + ), + ); + } + } + } catch (e) { + debugPrint('Error sending message: $e'); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Error: $e'), + duration: const Duration(seconds: 2), + ), + ); + } + } finally { + if (mounted) { + setState(() => _isSending = false); + } + } + } + + @override + Widget build(BuildContext context) { + if (_isLoading) { + return const Center( + child: Padding( + padding: EdgeInsets.all(16.0), + child: CircularProgressIndicator(), + ), + ); + } + + if (_error != null) { + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text(_error!, style: const TextStyle(color: AiTubeColors.onBackground)), + const SizedBox(height: 8), + ElevatedButton( + onPressed: () { + setState(() { + _isLoading = true; + _error = null; + }); + _initialize(); + }, + child: const Text('Retry'), + ), + ], + ), + ); + } + + return Container( + width: widget.isCompact ? double.infinity : 320, + decoration: BoxDecoration( + color: AiTubeColors.surface, + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: AiTubeColors.surfaceVariant, + width: 1, + ), + ), + child: Column( + children: [ + const Padding( + padding: EdgeInsets.all(16), + child: Row( + children: [ + Icon(Icons.chat, color: AiTubeColors.onBackground), + SizedBox(width: 8), + Text( + 'Live Chat', + style: TextStyle( + color: AiTubeColors.onBackground, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + Expanded(child: _buildMessageList()), + _buildMessageInput(), + ], + ), + ); + } + + @override + void dispose() { + debugPrint('ChatWidget: Disposing chat widget for video ${widget.videoId}'); + _disposed = true; + _reconnectTimer?.cancel(); + _messageController.dispose(); + _scrollController.dispose(); + + // Ensure chat room is left before disposal + _chatService.leaveRoom(widget.videoId).then((_) { + _chatService.dispose(); + }).catchError((error) { + debugPrint('ChatWidget: Error during disposal: $error'); + }); + + super.dispose(); + } +} \ No newline at end of file diff --git a/lib/widgets/maintenance_screen.dart b/lib/widgets/maintenance_screen.dart new file mode 100644 index 0000000000000000000000000000000000000000..e8a6a564c740107a78c84c9cafface2a041821b7 --- /dev/null +++ b/lib/widgets/maintenance_screen.dart @@ -0,0 +1,68 @@ +// lib/widgets/maintenance_screen.dart +import 'package:flutter/material.dart'; +import 'package:aitube2/theme/colors.dart'; + +class MaintenanceScreen extends StatelessWidget { + final Exception? error; + + const MaintenanceScreen({super.key, this.error}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.black87, + body: Center( + child: Container( + padding: const EdgeInsets.all(24), + constraints: const BoxConstraints(maxWidth: 500), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Icons.construction_rounded, + size: 80, + color: Colors.grey, + ), + const SizedBox(height: 24), + const Text( + 'AiTube is currently in maintenance', + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.grey, + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 16), + const Text( + 'Please ping @flngr on X for news', + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.grey, + fontSize: 16, + ), + ), + if (error != null) ...[ + const SizedBox(height: 24), + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.grey.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Text( + 'Error: $error', + style: const TextStyle( + color: Colors.grey, + fontSize: 14, + ), + ), + ), + ], + ], + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/search_box.dart b/lib/widgets/search_box.dart new file mode 100644 index 0000000000000000000000000000000000000000..ff8efd45479f34c46b436309667da82be085ca38 --- /dev/null +++ b/lib/widgets/search_box.dart @@ -0,0 +1,219 @@ +// lib/widgets/search_box.dart +import 'package:flutter/material.dart'; +import '../theme/colors.dart'; +import '../services/cache_service.dart'; + +class SearchBox extends StatefulWidget { + final TextEditingController controller; + final bool isSearching; + final bool enabled; + final Function(String) onSearch; + final VoidCallback onCancel; + + const SearchBox({ + super.key, + required this.controller, + required this.isSearching, + required this.enabled, + required this.onSearch, + required this.onCancel, + }); + + @override + State createState() => _SearchBoxState(); +} + +class _SearchBoxState extends State { + final _focusNode = FocusNode(); + final _cacheService = CacheService(); + bool _showSuggestions = false; + List _suggestions = []; + OverlayEntry? _overlayEntry; + final _layerLink = LayerLink(); + final bool _isComposing = false; + + @override + void initState() { + super.initState(); + _focusNode.addListener(_onFocusChanged); + widget.controller.addListener(_onSearchTextChanged); + } + + void _onFocusChanged() { + if (_focusNode.hasFocus) { + _showSuggestions = true; + _updateSuggestions(); + } else { + WidgetsBinding.instance.addPostFrameCallback((_) { + _hideSuggestions(); + }); + } + } + + void _onSearchTextChanged() { + if (_focusNode.hasFocus && !_isComposing) { + _updateSuggestions(); + } + } + + Future _updateSuggestions() async { + if (!mounted) return; + + final query = widget.controller.text.toLowerCase(); + if (query.isEmpty) { + setState(() { + _suggestions = []; + _hideOverlay(); + }); + return; + } + + try { + final results = await _cacheService.getCachedSearchResults(''); + if (!mounted) return; + + setState(() { + _suggestions = results + .map((result) => result.title) + .where((title) => title.toLowerCase().contains(query)) + .take(8) + .toList(); + + if (_suggestions.isNotEmpty && _focusNode.hasFocus) { + _showOverlay(); + } else { + _hideOverlay(); + } + }); + } catch (e) { + debugPrint('Error updating suggestions: $e'); + } + } + + void _showOverlay() { + _hideOverlay(); + + final RenderBox? renderBox = context.findRenderObject() as RenderBox?; + if (renderBox == null) return; + + final size = renderBox.size; + final offset = renderBox.localToGlobal(Offset.zero); + + _overlayEntry = OverlayEntry( + builder: (context) => Positioned( + left: offset.dx, + top: offset.dy + size.height + 5.0, + width: size.width, + child: CompositedTransformFollower( + link: _layerLink, + showWhenUnlinked: false, + offset: Offset(0.0, size.height + 5.0), + child: Material( + elevation: 4.0, + color: AiTubeColors.surface, + borderRadius: BorderRadius.circular(8), + child: Column( + mainAxisSize: MainAxisSize.min, + children: _suggestions.map((suggestion) { + return ListTile( + title: Text( + suggestion, + style: const TextStyle(color: AiTubeColors.onSurface), + ), + onTap: () { + widget.controller.text = suggestion; + _hideSuggestions(); + _handleSubmitted(suggestion); + }, + ); + }).toList(), + ), + ), + ), + ), + ); + + Overlay.of(context).insert(_overlayEntry!); + } + + void _hideOverlay() { + _overlayEntry?.remove(); + _overlayEntry = null; + } + + void _hideSuggestions() { + setState(() { + _showSuggestions = false; + _hideOverlay(); + }); + } + + void _handleSubmitted(String value) { + final trimmedValue = value.trim(); + if (trimmedValue.isNotEmpty) { + _hideSuggestions(); + FocusScope.of(context).unfocus(); + widget.onSearch(trimmedValue); + } + } + + @override + Widget build(BuildContext context) { + return Material( + color: Colors.transparent, + child: CompositedTransformTarget( + link: _layerLink, + child: TextFormField( + controller: widget.controller, + focusNode: _focusNode, + style: const TextStyle(color: AiTubeColors.onBackground), + enabled: widget.enabled, + textInputAction: TextInputAction.search, + onFieldSubmitted: _handleSubmitted, + onTapOutside: (_) { + FocusScope.of(context).unfocus(); + }, + decoration: InputDecoration( + hintText: 'Describe a video you want to generate...', + hintStyle: const TextStyle(color: AiTubeColors.onSurfaceVariant), + filled: true, + fillColor: AiTubeColors.surface, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(24), + borderSide: BorderSide.none, + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + suffixIcon: widget.isSearching + ? IconButton( + icon: const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator(strokeWidth: 2), + ), + onPressed: widget.onCancel, + ) + : IconButton( + icon: const Icon( + Icons.search, + color: AiTubeColors.onSurfaceVariant, + ), + onPressed: () => _handleSubmitted(widget.controller.text), + ), + ), + ), + ), + ); + } + + @override + void dispose() { + _hideOverlay(); + _focusNode.removeListener(_onFocusChanged); + _focusNode.dispose(); + widget.controller.removeListener(_onSearchTextChanged); + super.dispose(); + } +} \ No newline at end of file diff --git a/lib/widgets/video_card.dart b/lib/widgets/video_card.dart new file mode 100644 index 0000000000000000000000000000000000000000..7908ab1a6d9caeb8f3bcbbf36a018262e14a8cfa --- /dev/null +++ b/lib/widgets/video_card.dart @@ -0,0 +1,196 @@ +import 'package:flutter/material.dart'; +import '../theme/colors.dart'; +import '../models/video_result.dart'; + +class VideoCard extends StatelessWidget { + final VideoResult video; + + const VideoCard({ + super.key, + required this.video, + }); + + Widget _buildThumbnail() { + if (video.thumbnailUrl.isEmpty) { + return Container( + color: AiTubeColors.surfaceVariant, + child: const Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.movie_creation, + color: AiTubeColors.onSurfaceVariant, + size: 32, + ), + SizedBox(height: 8), + Text( + 'Generating preview...', + style: TextStyle( + color: AiTubeColors.onSurfaceVariant, + fontSize: 12, + ), + ), + ], + ), + ), + ); + } + + try { + if (video.thumbnailUrl.startsWith('data:image')) { + final uri = Uri.parse(video.thumbnailUrl); + final base64Data = uri.data?.contentAsBytes(); + + if (base64Data == null) { + throw Exception('Invalid image data'); + } + + return Image.memory( + base64Data, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + return _buildErrorThumbnail(); + }, + ); + } + + return Image.network( + video.thumbnailUrl, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + return _buildErrorThumbnail(); + }, + ); + } catch (e) { + return _buildErrorThumbnail(); + } + } + + Widget _buildErrorThumbnail() { + return Container( + color: AiTubeColors.surfaceVariant, + child: const Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.broken_image, + color: AiTubeColors.onSurfaceVariant, + size: 32, + ), + SizedBox(height: 8), + Text( + 'Preview unavailable', + style: TextStyle( + color: AiTubeColors.onSurfaceVariant, + fontSize: 12, + ), + ), + ], + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return Card( + clipBehavior: Clip.antiAlias, + margin: EdgeInsets.zero, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AspectRatio( + aspectRatio: 16 / 9, + child: Stack( + fit: StackFit.expand, + children: [ + _buildThumbnail(), + if (video.isLatent) + Positioned( + right: 8, + top: 8, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.7), + borderRadius: BorderRadius.circular(4), + ), + child: const Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.ac_unit, + size: 16, + color: AiTubeColors.onBackground, + ), + SizedBox(width: 4), + Text( + 'Latent', + style: TextStyle( + color: AiTubeColors.onBackground, + fontSize: 12, + ), + ), + ], + ), + ), + ), + ], + ), + ), + Container( + padding: const EdgeInsets.all(12), + color: AiTubeColors.surface, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const CircleAvatar( + radius: 16, + backgroundColor: AiTubeColors.surfaceVariant, + child: Icon( + Icons.play_arrow, + color: AiTubeColors.onSurfaceVariant, + size: 20, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + video.title, + style: const TextStyle( + color: AiTubeColors.onBackground, + fontWeight: FontWeight.w500, + fontSize: 14, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 4), + const Text( + 'Generated using LTX Video', + style: TextStyle( + color: AiTubeColors.onSurfaceVariant, + fontSize: 12, + ), + ), + ], + ), + ), + ], + ), + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/video_player_widget.dart b/lib/widgets/video_player_widget.dart new file mode 100644 index 0000000000000000000000000000000000000000..1326bad0143897e9a32a7cceec99af6a9b863671 --- /dev/null +++ b/lib/widgets/video_player_widget.dart @@ -0,0 +1,667 @@ +// lib/widgets/video_player_widget.dart + +import 'dart:async'; +import 'package:aitube2/config/config.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/foundation.dart' show kDebugMode; +import 'package:video_player/video_player.dart'; +import '../models/video_result.dart'; +import '../services/clip_queue_manager.dart'; +import '../theme/colors.dart'; + +class VideoPlayerWidget extends StatefulWidget { + final VideoResult video; + final String? initialThumbnailUrl; + final bool autoPlay; + final double borderRadius; + final VoidCallback? onVideoLoaded; + + const VideoPlayerWidget({ + super.key, + required this.video, + this.initialThumbnailUrl, + this.autoPlay = true, + this.borderRadius = 12, + this.onVideoLoaded, + }); + + @override + State createState() => _VideoPlayerWidgetState(); +} + +class _VideoPlayerWidgetState extends State { + late final ClipQueueManager _queueManager; + VideoPlayerController? _currentController; + VideoPlayerController? _nextController; + VideoClip? _currentClip; + bool _isPlaying = false; + bool _isLoading = false; + bool _isInitialLoad = true; + + + double _loadingProgress = 0.0; + Timer? _progressTimer; + Timer? _debugTimer; + Timer? _playbackTimer; // New timer for tracking playback duration + bool _isDisposed = false; + + Duration _currentPlaybackPosition = Duration.zero; + Timer? _positionTrackingTimer; + + bool _startedInitialPlayback = false; + Timer? _nextClipCheckTimer; + + @override + void initState() { + super.initState(); + _initializePlayer(); + // if (kDebugMode) { _startDebugPrinting(); } + } + + void _startNextClipCheck() { + _nextClipCheckTimer?.cancel(); + _nextClipCheckTimer = Timer.periodic(const Duration(milliseconds: 100), (timer) { + if (_isDisposed || !_isPlaying) { + timer.cancel(); + return; + } + + final nextClip = _queueManager.nextReadyClip; + if (nextClip != null) { + timer.cancel(); + _onVideoCompleted(); + } + }); + } + + void _checkBufferAndStartPlayback() { + if (_isDisposed || _startedInitialPlayback) return; + + final readyClips = _queueManager.clipBuffer.where((c) => c.isReady).length; + final totalClips = _queueManager.clipBuffer.length; + final bufferPercentage = (readyClips / totalClips * 100); + + if (bufferPercentage >= Configuration.instance.minimumBufferPercentToStartPlayback) { + _startedInitialPlayback = true; + _startInitialPlayback(); + } else { + // Schedule another check + Future.delayed(const Duration(milliseconds: 50), _checkBufferAndStartPlayback); + } + } + + Future _startInitialPlayback() async { + final nextClip = _queueManager.currentClip; + if (nextClip?.isReady == true && !nextClip!.isPlaying) { + _queueManager.startPlayingClip(nextClip); + await _playClip(nextClip); + } + + setState(() { + _isLoading = false; + _isInitialLoad = false; + }); + } + + void _startPlaybackTimer() { + _playbackTimer?.cancel(); + _nextClipCheckTimer?.cancel(); + + _playbackTimer = Timer(Configuration.instance.actualClipPlaybackDuration, () { + if (_isDisposed || !_isPlaying) return; + + final nextClip = _queueManager.nextReadyClip; + + if (nextClip != null) { + _onVideoCompleted(); + } else { + // Reset current clip + _currentController?.seekTo(Duration.zero); + _currentPlaybackPosition = Duration.zero; + + // Start checking for next clip availability + _startNextClipCheck(); + } + }); + + _startPositionTracking(); + } + + void _startPositionTracking() { + _positionTrackingTimer?.cancel(); + _positionTrackingTimer = Timer.periodic(const Duration(milliseconds: 50), (_) { + if (_isDisposed || !_isPlaying) return; + + final controller = _currentController; + if (controller != null && controller.value.isInitialized) { + _currentPlaybackPosition = controller.value.position; + } + }); + } + + void _togglePlayback() { + if (_isLoading) return; + + final controller = _currentController; + if (controller == null) return; + + setState(() => _isPlaying = !_isPlaying); + + if (_isPlaying) { + // Restore previous position before playing + controller.seekTo(_currentPlaybackPosition); + controller.play(); + _startPlaybackTimer(); + } else { + controller.pause(); + _playbackTimer?.cancel(); + _positionTrackingTimer?.cancel(); + } + } + + Future _playClip(VideoClip clip) async { + if (_isDisposed || clip.base64Data == null) return; + + try { + VideoPlayerController? newController; + + if (_nextController != null) { + debugPrint('Using preloaded controller for clip ${clip.seed}'); + newController = _nextController; + _nextController = null; + } else { + debugPrint('Creating new controller for clip ${clip.seed}'); + newController = VideoPlayerController.networkUrl( + Uri.parse(clip.base64Data!), + ); + await newController.initialize(); + } + + if (_isDisposed || newController == null) { + newController?.dispose(); + return; + } + + newController.setLooping(true); + newController.setVolume(0.0); + newController.setPlaybackSpeed(Configuration.instance.clipPlaybackSpeed); + + final oldController = _currentController; + final oldClip = _currentClip; + + _queueManager.startPlayingClip(clip); + _currentPlaybackPosition = Duration.zero; // Reset for new clip + + setState(() { + _currentController = newController; + _currentClip = clip; + _isPlaying = widget.autoPlay; + }); + + if (widget.autoPlay) { + await newController.play(); + debugPrint('Started playback of clip ${clip.seed}'); + _startPlaybackTimer(); + } + + await oldController?.dispose(); + if (oldClip != null && oldClip != clip) { + _queueManager.markCurrentClipAsPlayed(); + } + + widget.onVideoLoaded?.call(); + await _preloadNextClip(); + _ensureBufferFull(); + + } catch (e) { + debugPrint('Error playing clip: $e'); + if (!_isDisposed) { + setState(() => _isLoading = true); + await Future.delayed(const Duration(milliseconds: 500)); + await _waitForClipAndPlay(); + } + } + } + + @override + void dispose() { + _isDisposed = true; + _currentController?.dispose(); + _nextController?.dispose(); + _progressTimer?.cancel(); + _debugTimer?.cancel(); + _playbackTimer?.cancel(); + _nextClipCheckTimer?.cancel(); + _positionTrackingTimer?.cancel(); + _queueManager.dispose(); + super.dispose(); + } + + ////////////////////////// + + + Future _onVideoCompleted() async { + if (_isDisposed) return; + + debugPrint('\nHandling video completion'); + _playbackTimer?.cancel(); // Cancel current playback timer + + // Get next clip before cleaning up current + final nextClip = _queueManager.nextReadyClip; + if (nextClip == null) { + // debugPrint('No next clip ready, resetting current playback'); + _currentController?.seekTo(Duration.zero); + _startPlaybackTimer(); // Restart playback timer + return; + } + + // Mark current as played and move to history + if (_currentClip != null) { + // debugPrint('Marking current clip ${_currentClip!.seed} as played'); + _queueManager.markCurrentClipAsPlayed(); + _currentClip = null; + } + + // Important: Mark the next clip as playing BEFORE transitioning the video + _queueManager.startPlayingClip(nextClip); + // debugPrint('Marked next clip ${nextClip.seed} as playing in queue'); + + // Transition to next clip + if (_nextController != null) { + // debugPrint('Using preloaded controller for next clip ${nextClip.seed}'); + final oldController = _currentController; + setState(() { + _currentController = _nextController; + _nextController = null; + _currentClip = nextClip; + _isPlaying = true; + }); + + await _currentController?.play(); + _startPlaybackTimer(); // Start timer for new clip + await oldController?.dispose(); + + // Start preloading next + await _preloadNextClip(); + + } else { + // debugPrint('No preloaded controller, playing next clip ${nextClip.seed} directly'); + await _playClip(nextClip); + } + } + + void _startDebugPrinting() { + _debugTimer = Timer.periodic(const Duration(seconds: 5), (_) { + if (!_isDisposed) { + _queueManager.printQueueState(); + _logPlaybackStatus(); + } + }); + } + + void _logPlaybackStatus() { + if (kDebugMode) { + final controller = _currentController; + if (controller != null && controller.value.isInitialized) { + final position = controller.value.position; + final duration = controller.value.duration; + debugPrint('Playback status: ${position.inSeconds}s / ${duration.inSeconds}s' + ' (${_isPlaying ? "playing" : "paused"})'); + debugPrint('Current clip: ${_currentClip?.seed}, Next controller ready: ${_nextController != null}'); + } + } + } + + Future _initializePlayer() async { + if (_isDisposed) return; + + setState(() => _isLoading = true); + + if (_isInitialLoad) { + _startLoadingProgress(); + } + + _queueManager = ClipQueueManager( + video: widget.video, + onQueueUpdated: () { + _onQueueUpdated(); + // Check buffer status whenever queue updates + _checkBufferAndStartPlayback(); + }, + ); + + // Initialize queue manager but don't await it + _queueManager.initialize().then((_) { + if (!_isDisposed) { + _waitForClipAndPlay(); + } + }); + } + + void _startLoadingProgress() { + _progressTimer?.cancel(); + _loadingProgress = 0.0; + + const totalDuration = Duration(seconds: 12); + const updateInterval = Duration(milliseconds: 50); + final steps = totalDuration.inMilliseconds / updateInterval.inMilliseconds; + final increment = 1.0 / steps; + + _progressTimer = Timer.periodic(updateInterval, (timer) { + if (_isDisposed) { + timer.cancel(); + return; + } + + setState(() { + _loadingProgress += increment; + if (_loadingProgress >= 1.0) { + _progressTimer?.cancel(); + } + }); + }); + } + + void _onQueueUpdated() { + if (_isDisposed) return; + setState(() {}); + } + + Future _waitForClipAndPlay() async { + if (_isDisposed) return; + + try { + // Start periodic buffer checks + _checkBufferAndStartPlayback(); + } catch (e) { + debugPrint('Error waiting for clip: $e'); + if (!_isDisposed) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Error loading video: $e')), + ); + setState(() { + _isLoading = false; + _isInitialLoad = false; + }); + } + } + } + + // New method to ensure buffer stays full + void _ensureBufferFull() { + if (_isDisposed) return; + // debugPrint('Ensuring buffer is full...'); + // Let the queue manager know it should start new generations + // This will trigger generation of new clips up to capacity + _queueManager.fillBuffer(); + } + + Future _preloadNextClip() async { + if (_isDisposed) return; + + try { + // Always try to preload the next ready clip + final nextReadyClip = _queueManager.nextReadyClip; + + if (nextReadyClip?.base64Data != null && + nextReadyClip != _currentClip && + !nextReadyClip!.isPlaying) { + // debugPrint('Preloading next clip (seed: ${nextReadyClip.seed})'); + + final nextController = VideoPlayerController.networkUrl( + Uri.parse(nextReadyClip.base64Data!), + ); + + await nextController.initialize(); + + if (_isDisposed) { + nextController.dispose(); + return; + } + + // we always keep things looping. We never want any video to stop. + nextController.setLooping(true); + nextController.setVolume(0.0); + nextController.setPlaybackSpeed(Configuration.instance.clipPlaybackSpeed); + + _nextController?.dispose(); + _nextController = nextController; + + // debugPrint('Successfully preloaded next clip'); + } + + // Always ensure we're generating new clips after preloading + _ensureBufferFull(); + + } catch (e) { + debugPrint('Error preloading next clip: $e'); + _nextController?.dispose(); + _nextController = null; + } + } + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) { + final controller = _currentController; + final aspectRatio = controller?.value.aspectRatio ?? 16/9; + final playerHeight = constraints.maxWidth / aspectRatio; + + return SizedBox( + width: constraints.maxWidth, + height: playerHeight, + child: Stack( + fit: StackFit.expand, + children: [ + // Base layer: Placeholder or Video + ClipRRect( + borderRadius: BorderRadius.circular(widget.borderRadius), + child: Container( + color: AiTubeColors.surfaceVariant, + child: controller?.value.isInitialized ?? false + ? VideoPlayer(controller!) + : _buildPlaceholder(), + ), + ), + + // Loading overlay with translucent background + if (_isLoading) + ClipRRect( + borderRadius: BorderRadius.circular(widget.borderRadius), + child: Container( + color: Colors.black54, // Semi-transparent overlay + child: const Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(height: 16), + Text( + 'Generating video...', + style: TextStyle( + color: Colors.white, + fontSize: 16, + ), + ), + ], + ), + ) + ), + ), + + // Play/Pause button overlay + if (controller?.value.isInitialized ?? false) + Positioned( + left: 16, + bottom: 16, + child: GestureDetector( + onTap: _togglePlayback, + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.6), + borderRadius: BorderRadius.circular(24), + ), + child: Icon( + _isPlaying ? Icons.pause : Icons.play_arrow, + color: Colors.white, + size: 24, + ), + ), + ), + ), + + _buildBufferStatus(true), + // Debug stats overlay + /* + if (kDebugMode) + Positioned( + right: 16, + top: 16, + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.6), + borderRadius: BorderRadius.circular(8), + ), + child: Text( + _formatQueueStats(), + style: const TextStyle( + color: Colors.white, + fontSize: 12, + ), + ), + ), + ), + */ + ], + ), + ); + }, + ); + } + + String _formatQueueStats() { + final stats = _queueManager.getBufferStats(); + final currentClipInfo = _currentClip != null + ? '\nPlaying: ${_currentClip!.seed}' + : ''; + final nextClipInfo = _nextController != null + ? '\nNext clip preloaded' + : ''; + + return 'Queue: ${stats['readyClips']}/${stats['bufferSize']} ready\n' + 'Gen: ${stats['activeGenerations']} active' + '$currentClipInfo$nextClipInfo'; + } + + Widget _buildPlaceholder() { + if (widget.initialThumbnailUrl?.isEmpty ?? true) { + return const Center( + child: Icon( + Icons.play_circle_outline, + size: 64, + color: AiTubeColors.onSurfaceVariant, + ), + ); + } + + try { + if (widget.initialThumbnailUrl!.startsWith('data:image')) { + final uri = Uri.parse(widget.initialThumbnailUrl!); + final base64Data = uri.data?.contentAsBytes(); + + if (base64Data == null) { + throw Exception('Invalid image data'); + } + + return Image.memory( + base64Data, + fit: BoxFit.cover, + errorBuilder: (_, __, ___) => _buildErrorPlaceholder(), + ); + } + + return Image.network( + widget.initialThumbnailUrl!, + fit: BoxFit.cover, + errorBuilder: (_, __, ___) => _buildErrorPlaceholder(), + ); + } catch (e) { + return _buildErrorPlaceholder(); + } + } + + Widget _buildBufferStatus(bool showDuringLoading) { + final readyOrPlayingClips = _queueManager.clipBuffer.where((c) => c.isReady || c.isPlaying).length; + final totalClips = _queueManager.clipBuffer.length; + final bufferPercentage = (readyOrPlayingClips / totalClips * 100).round(); + + // since we are playing clips at a reduced speed, they each last "longer" + // eg a video playing back at 0.5 speed will see its duration multiplied by 2 + final actualDurationPerClip = Configuration.instance.actualClipPlaybackDuration; + + final remainingSeconds = readyOrPlayingClips * actualDurationPerClip.inSeconds; + + // Don't show 0% during initial loading + if (!showDuringLoading && bufferPercentage == 0) { + return const SizedBox.shrink(); + } + + return Positioned( + right: 16, + bottom: 16, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.6), + borderRadius: BorderRadius.circular(16), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + _getBufferIcon(bufferPercentage), + color: _getBufferStatusColor(bufferPercentage), + size: 16, + ), + const SizedBox(width: 4), + Text( + _isLoading + ? 'Buffering $bufferPercentage%' + : '$bufferPercentage% (${remainingSeconds}s)', + style: const TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ); + } + + IconData _getBufferIcon(int percentage) { + if (percentage >= 40) return Icons.network_wifi; + if (percentage >= 30) return Icons.network_wifi_3_bar; + if (percentage >= 20) return Icons.network_wifi_2_bar; + return Icons.network_wifi_1_bar; + } + + Color _getBufferStatusColor(int percentage) { + if (percentage >= 30) return Colors.green; + if (percentage >= 20) return Colors.orange; + return Colors.red; + } + + Widget _buildErrorPlaceholder() { + return const Center( + child: Icon( + Icons.broken_image, + size: 64, + color: AiTubeColors.onSurfaceVariant, + ), + ); + } +} \ No newline at end of file diff --git a/linux/.gitignore b/linux/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..d3896c98444fbe7288d434169a28c532258a4466 --- /dev/null +++ b/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..2d773b27386a09a87356e02b1788cf75bb3befc8 --- /dev/null +++ b/linux/CMakeLists.txt @@ -0,0 +1,145 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "aitube2") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.aitube2") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Define the application target. To change its name, change BINARY_NAME above, +# not the value here, or `flutter run` will no longer work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/linux/flutter/CMakeLists.txt b/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..d5bd01648a96d50136b2a97c8bb9aa5b711f6c39 --- /dev/null +++ b/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000000000000000000000000000000000000..38dd0bc6c83cbcb9a22659eb418320ea19f5684e --- /dev/null +++ b/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,19 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include +#include + +void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); + flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); +} diff --git a/linux/flutter/generated_plugin_registrant.h b/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000000000000000000000000000000000000..e0f0a47bc08f30b550b47b01de4c9206b6824dd9 --- /dev/null +++ b/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake new file mode 100644 index 0000000000000000000000000000000000000000..65240e99cc3e4b3ccf03e56ed6275a784a22e5d7 --- /dev/null +++ b/linux/flutter/generated_plugins.cmake @@ -0,0 +1,25 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + flutter_secure_storage_linux + url_launcher_linux +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/linux/main.cc b/linux/main.cc new file mode 100644 index 0000000000000000000000000000000000000000..e7c5c54370372a4cdde7288a32733998d87bd767 --- /dev/null +++ b/linux/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/linux/my_application.cc b/linux/my_application.cc new file mode 100644 index 0000000000000000000000000000000000000000..8f0180cfac6df0f13c31cab42eef3bbe5dc6f8c9 --- /dev/null +++ b/linux/my_application.cc @@ -0,0 +1,124 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "aitube2"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "aitube2"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GApplication::startup. +static void my_application_startup(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/linux/my_application.h b/linux/my_application.h new file mode 100644 index 0000000000000000000000000000000000000000..72271d5e41701cfbffad74d38640bf9cc7c1f7be --- /dev/null +++ b/linux/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/macos/.gitignore b/macos/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..746adbb6b9e14b7e685c91e280a4d37a672afbd8 --- /dev/null +++ b/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/macos/Flutter/Flutter-Debug.xcconfig b/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000000000000000000000000000000000000..4b81f9b2d200fc807eed25bca04dff9ee9dcc45f --- /dev/null +++ b/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/Flutter-Release.xcconfig b/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000000000000000000000000000000000000..5caa9d1579e481a5894c37e2e56b0da062e95b35 --- /dev/null +++ b/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000000000000000000000000000000000000..4c7676068627a6f6ef518f9ae331e93931739059 --- /dev/null +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,22 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import flutter_secure_storage_macos +import path_provider_foundation +import shared_preferences_foundation +import url_launcher_macos +import video_player_avfoundation +import webview_flutter_wkwebview + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) + FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) + FLTWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "FLTWebViewFlutterPlugin")) +} diff --git a/macos/Podfile b/macos/Podfile new file mode 100644 index 0000000000000000000000000000000000000000..c795730db8edb4751a1c18bff5cb2622133a2c68 --- /dev/null +++ b/macos/Podfile @@ -0,0 +1,43 @@ +platform :osx, '10.14' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000000000000000000000000000000000000..2e71d40008207b222bdd9f07f9fc6a7a61df2281 --- /dev/null +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,705 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* aitube2.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "aitube2.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* aitube2.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* aitube2.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.aitube2.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/aitube2.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/aitube2"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.aitube2.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/aitube2.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/aitube2"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.aitube2.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/aitube2.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/aitube2"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + DefaultConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + DefaultConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + DefaultConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + DefaultConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + DefaultConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + DefaultConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000000000000000000000000000000000..18d981003d68d0546c4804ac2ff47dd97c6e7921 --- /dev/null +++ b/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000000000000000000000000000000000000..2c5947fa8f72b8f40893497ac5b11670a81c1d20 --- /dev/null +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macos/Runner.xcworkspace/contents.xcworkspacedata b/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000000000000000000000000000000000..1d526a16ed0f1cd0c2409d848bf489b93fefa3b2 --- /dev/null +++ b/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000000000000000000000000000000000..18d981003d68d0546c4804ac2ff47dd97c6e7921 --- /dev/null +++ b/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/macos/Runner/AppDelegate.swift b/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000000000000000000000000000000000000..8e02df288835694650d8c57a5fffefcd8d7ff93f --- /dev/null +++ b/macos/Runner/AppDelegate.swift @@ -0,0 +1,9 @@ +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..a2ec33f19f110ebff51a23342d7bc29ec9a1aaa6 --- /dev/null +++ b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000000000000000000000000000000000000..0066c008ba292c746ef170676f5643de1742f589 --- /dev/null +++ b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6232e5815af17e25e0268b2fec7aea9e068cc92ec709e9605c2b31df4ff2a313 +size 102994 diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000000000000000000000000000000000000..5dfe938da89613465fa481046638640ba60bbb6f --- /dev/null +++ b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:15591f03f31313af6fd644ed0512106cc04365130b8b73244f1cfa6dddfb4400 +size 5680 diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000000000000000000000000000000000000..50c377b64308af2f33587a03857b8da38944019b --- /dev/null +++ b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cc6928b5adfc00dbf526192e2705dd9af641cdf20ab4c6c7ca7cd4936dca59f0 +size 520 diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000000000000000000000000000000000000..045ed5a383f25d0d4ea0ffe99f7525da62d54c04 --- /dev/null +++ b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:416efd77cde932d42ef34168da24dd428a495b1f4f34bbbd125a18d2add186a2 +size 14142 diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 0000000000000000000000000000000000000000..9fc6a25dd24671b4411c7202e5e17f3602665160 --- /dev/null +++ b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ae8c4458e41f1e28b1e851ed87d3268d4a0351ceea427fa2a84cc94ddfb6d4c5 +size 1066 diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000000000000000000000000000000000000..ebe59bef45b2a2bd3abb9648208897fe8090a36e --- /dev/null +++ b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0a91c6c1bf242e54ee179e34629e9ef3e8a6d286c0fce01e302280a8be9277e6 +size 36406 diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000000000000000000000000000000000000..3f3dd63f18716f1ed06d0239c18e0498f011c44d --- /dev/null +++ b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6ad229623498e5f1277800db3ab7cb11faf85eb2569e1213f7d8e55003c07b42 +size 2218 diff --git a/macos/Runner/Base.lproj/MainMenu.xib b/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000000000000000000000000000000000000..80e867a4e06b4dc26d0a2b327cbd54041addc50a --- /dev/null +++ b/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macos/Runner/Configs/AppInfo.xcconfig b/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000000000000000000000000000000000000..92972328942c556be461c343a55970cb81d97f5a --- /dev/null +++ b/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = aitube2 + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.aitube2 + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2024 com.example. All rights reserved. diff --git a/macos/Runner/Configs/Debug.xcconfig b/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000000000000000000000000000000000000..36b0fd9464f45b33f482e64bea579787e142affa --- /dev/null +++ b/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/macos/Runner/Configs/Release.xcconfig b/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000000000000000000000000000000000000..dff4f49561c816f70eaea557d889d31cf63447ac --- /dev/null +++ b/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/macos/Runner/Configs/Warnings.xcconfig b/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000000000000000000000000000000000000..42bcbf4780b187e80025b7917058bcb882012aa1 --- /dev/null +++ b/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/macos/Runner/DebugProfile.entitlements b/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000000000000000000000000000000000000..dddb8a30c851e7ef5b16a9108934bd1217b6b43f --- /dev/null +++ b/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/macos/Runner/Info.plist b/macos/Runner/Info.plist new file mode 100644 index 0000000000000000000000000000000000000000..4789daa6a443eefa7f3f729164a18351e06e9800 --- /dev/null +++ b/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/macos/Runner/MainFlutterWindow.swift b/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000000000000000000000000000000000000..3cc05eb23491684aae1c51ce94668a142bad5125 --- /dev/null +++ b/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/macos/Runner/Release.entitlements b/macos/Runner/Release.entitlements new file mode 100644 index 0000000000000000000000000000000000000000..852fa1a4728ae4789e3bca55dd07caef3b41f2a5 --- /dev/null +++ b/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/macos/RunnerTests/RunnerTests.swift b/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000000000000000000000000000000000000..61f3bd1fc504c3b78271416d8cfd14faa1dae2b4 --- /dev/null +++ b/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000000000000000000000000000000000000..aa6043a943535bc8d107eb631ab03103bf9a7835 --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,714 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: "direct main" + description: + name: async + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" + source: hosted + version: "2.13.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + csslib: + dependency: transitive + description: + name: csslib + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + url: "https://pub.dev" + source: hosted + version: "1.0.8" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + flutter_secure_storage: + dependency: "direct main" + description: + name: flutter_secure_storage + sha256: "165164745e6afb5c0e3e3fcc72a012fb9e58496fb26ffb92cf22e16a821e85d0" + url: "https://pub.dev" + source: hosted + version: "9.2.2" + flutter_secure_storage_linux: + dependency: transitive + description: + name: flutter_secure_storage_linux + sha256: "4d91bfc23047422cbcd73ac684bc169859ee766482517c22172c86596bf1464b" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + flutter_secure_storage_macos: + dependency: transitive + description: + name: flutter_secure_storage_macos + sha256: "1693ab11121a5f925bbea0be725abfcfbbcf36c1e29e571f84a0c0f436147a81" + url: "https://pub.dev" + source: hosted + version: "3.1.2" + flutter_secure_storage_platform_interface: + dependency: transitive + description: + name: flutter_secure_storage_platform_interface + sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_secure_storage_web: + dependency: transitive + description: + name: flutter_secure_storage_web + sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + flutter_secure_storage_windows: + dependency: transitive + description: + name: flutter_secure_storage_windows + sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + flutter_staggered_grid_view: + dependency: "direct main" + description: + name: flutter_staggered_grid_view + sha256: "19e7abb550c96fbfeb546b23f3ff356ee7c59a019a651f8f102a4ba9b7349395" + url: "https://pub.dev" + source: hosted + version: "0.7.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + html: + dependency: transitive + description: + name: html + sha256: "1fc58edeaec4307368c60d59b7e15b9d658b57d7f3125098b6294153c75337ec" + url: "https://pub.dev" + source: hosted + version: "0.15.5" + http: + dependency: "direct main" + description: + name: http + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 + url: "https://pub.dev" + source: hosted + version: "1.2.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + huggingface_client: + dependency: "direct main" + description: + name: huggingface_client + sha256: a276805db74a0a8ef83306e6492e5b0cf6c7a15d209d7492c4e875035f56ba37 + url: "https://pub.dev" + source: hosted + version: "1.6.2" + idb_shim: + dependency: "direct main" + description: + name: idb_shim + sha256: "9e7ec816139bfafb69ae4b3668ad29dbd43c53428d6eb31f9332d42bd4fa7205" + url: "https://pub.dev" + source: hosted + version: "2.6.1+7" + intl: + dependency: transitive + description: + name: intl + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.dev" + source: hosted + version: "0.20.2" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + url: "https://pub.dev" + source: hosted + version: "10.0.9" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + url: "https://pub.dev" + source: hosted + version: "3.0.9" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" + source: hosted + version: "0.12.17" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "8c4967f8b7cb46dc914e178daa29813d83ae502e0529d7b0478330616a691ef7" + url: "https://pub.dev" + source: hosted + version: "2.2.14" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 + url: "https://pub.dev" + source: hosted + version: "2.4.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + sembast: + dependency: transitive + description: + name: sembast + sha256: "61f893e50fe2fa7d14529d275d45ac31871d7ad2ae3a745f9aa3afc0b447d75b" + url: "https://pub.dev" + source: hosted + version: "3.7.5+2" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "95f9997ca1fb9799d494d0cb2a780fd7be075818d59f00c43832ed112b158a82" + url: "https://pub.dev" + source: hosted + version: "2.3.3" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "3b9febd815c9ca29c9e3520d50ec32f49157711e143b7a4ca039eb87e8ade5ab" + url: "https://pub.dev" + source: hosted + version: "2.3.3" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "07e050c7cd39bad516f8d64c455f04508d09df104be326d8c02551590a0d513d" + url: "https://pub.dev" + source: hosted + version: "2.5.3" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e + url: "https://pub.dev" + source: hosted + version: "2.4.2" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.dev" + source: hosted + version: "1.10.1" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + synchronized: + dependency: "direct main" + description: + name: synchronized + sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225" + url: "https://pub.dev" + source: hosted + version: "3.3.0+3" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + url: "https://pub.dev" + source: hosted + version: "0.7.4" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" + url: "https://pub.dev" + source: hosted + version: "6.3.1" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193" + url: "https://pub.dev" + source: hosted + version: "6.3.14" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e + url: "https://pub.dev" + source: hosted + version: "6.3.1" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "769549c999acdb42b8bcfa7c43d72bf79a382ca7441ab18a808e101149daf672" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" + url: "https://pub.dev" + source: hosted + version: "2.3.3" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "44cf3aabcedde30f2dba119a9dea3b0f2672fbe6fa96e85536251d678216b3c4" + url: "https://pub.dev" + source: hosted + version: "3.1.3" + uuid: + dependency: "direct main" + description: + name: uuid + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff + url: "https://pub.dev" + source: hosted + version: "4.5.1" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + video_player: + dependency: "direct main" + description: + name: video_player + sha256: "4a8c3492d734f7c39c2588a3206707a05ee80cef52e8c7f3b2078d430c84bc17" + url: "https://pub.dev" + source: hosted + version: "2.9.2" + video_player_android: + dependency: transitive + description: + name: video_player_android + sha256: "391e092ba4abe2f93b3e625bd6b6a6ec7d7414279462c1c0ee42b5ab8d0a0898" + url: "https://pub.dev" + source: hosted + version: "2.7.16" + video_player_avfoundation: + dependency: transitive + description: + name: video_player_avfoundation + sha256: "0b146e5d82e886ff43e5a46c6bcbe390761b802864a6e2503eb612d69a405dfa" + url: "https://pub.dev" + source: hosted + version: "2.6.3" + video_player_platform_interface: + dependency: transitive + description: + name: video_player_platform_interface + sha256: "229d7642ccd9f3dc4aba169609dd6b5f3f443bb4cc15b82f7785fcada5af9bbb" + url: "https://pub.dev" + source: hosted + version: "6.2.3" + video_player_web: + dependency: transitive + description: + name: video_player_web + sha256: "881b375a934d8ebf868c7fb1423b2bfaa393a0a265fa3f733079a86536064a10" + url: "https://pub.dev" + source: hosted + version: "2.3.3" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + url: "https://pub.dev" + source: hosted + version: "15.0.0" + web: + dependency: transitive + description: + name: web + sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb + url: "https://pub.dev" + source: hosted + version: "1.1.0" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + url: "https://pub.dev" + source: hosted + version: "0.1.6" + web_socket_channel: + dependency: "direct main" + description: + name: web_socket_channel + sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + webview_flutter: + dependency: "direct main" + description: + name: webview_flutter + sha256: "889a0a678e7c793c308c68739996227c9661590605e70b1f6cf6b9a6634f7aec" + url: "https://pub.dev" + source: hosted + version: "4.10.0" + webview_flutter_android: + dependency: transitive + description: + name: webview_flutter_android + sha256: "285cedfd9441267f6cca8843458620b5fda1af75b04f5818d0441acda5d7df19" + url: "https://pub.dev" + source: hosted + version: "4.1.0" + webview_flutter_platform_interface: + dependency: transitive + description: + name: webview_flutter_platform_interface + sha256: d937581d6e558908d7ae3dc1989c4f87b786891ab47bb9df7de548a151779d8d + url: "https://pub.dev" + source: hosted + version: "2.10.0" + webview_flutter_wkwebview: + dependency: transitive + description: + name: webview_flutter_wkwebview + sha256: b7e92f129482460951d96ef9a46b49db34bd2e1621685de26e9eaafd9674e7eb + url: "https://pub.dev" + source: hosted + version: "3.16.3" + win32: + dependency: transitive + description: + name: win32 + sha256: "84ba388638ed7a8cb3445a320c8273136ab2631cd5f2c57888335504ddab1bc2" + url: "https://pub.dev" + source: hosted + version: "5.8.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + yaml: + dependency: "direct main" + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" +sdks: + dart: ">=3.7.0-0 <4.0.0" + flutter: ">=3.24.0" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000000000000000000000000000000000000..b57f42efc18795874a6b512d1ef39d1ddef59614 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,105 @@ +name: aitube2 +description: "An interactive latent video search platform." +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: '>=3.5.4 <4.0.0' + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.8 + + huggingface_client: ^1.6.2 + + path_provider: ^2.0.15 + flutter_staggered_grid_view: 0.7.0 + flutter_secure_storage: ^9.0.0 + video_player: ^2.9.2 + shared_preferences: ^2.2.2 + webview_flutter: ^4.10.0 + url_launcher: ^6.3.1 + uuid: ^4.5.1 + yaml: ^3.1.2 + http: ^1.2.2 + web_socket_channel: ^3.0.1 + idb_shim: ^2.6.1+7 + synchronized: ^3.3.0+3 + async: ^2.11.0 + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^4.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + assets: + - assets/config/ + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/to/asset-from-package + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/to/font-from-package diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..2d261563a9986b95389892e9f97501d54e612825 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +gradio==5.6.0 +gradio_client==1.4.3 +requests==2.32.3 +aiohttp==3.10.5 +huggingface-hub==0.25.1 +fal_client==0.5.6 \ No newline at end of file diff --git a/test/widget_test.dart b/test/widget_test.dart new file mode 100644 index 0000000000000000000000000000000000000000..01ac5d8c5db1c8b26749733dfb82b55f0409a7eb --- /dev/null +++ b/test/widget_test.dart @@ -0,0 +1,29 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} diff --git a/test_maintenance.sh b/test_maintenance.sh new file mode 100755 index 0000000000000000000000000000000000000000..8696912895cdba40fb118bc2b060bcd251406840 --- /dev/null +++ b/test_maintenance.sh @@ -0,0 +1,71 @@ +#!/bin/bash + +# Check if maintenance mode is currently enabled +echo "Current maintenance mode status:" +MAINTENANCE_MODE=true python3 -c "from api_config import MAINTENANCE_MODE; print(f'Maintenance mode is: {MAINTENANCE_MODE}')" + +echo "" +echo "Testing websocket in normal mode:" +# Test the websocket connection (should work normally) +MAINTENANCE_MODE=false python3 -c " +import asyncio +from aiohttp import web +from api import init_app + +async def test(): + app = await init_app() + print('API initialized successfully') + print('Maintenance mode is disabled, WebSocket connections should work normally') + return app + +if __name__ == '__main__': + asyncio.run(test()) +" + +echo "" +echo "Testing websocket in maintenance mode:" +# Test the websocket in maintenance mode +MAINTENANCE_MODE=true python3 -c " +import asyncio +from aiohttp import web +from api import init_app + +async def test(): + app = await init_app() + print('API initialized successfully') + print('Maintenance mode is enabled, WebSocket connections should be rejected') + return app + +if __name__ == '__main__': + asyncio.run(test()) +" + +echo "" +echo "Testing HTTP server in maintenance mode:" +# Start the server in maintenance mode to test HTTP serving +# This will run in background for 5 seconds +MAINTENANCE_MODE=true python3 -c " +import asyncio +import time +from aiohttp import web +from api import init_app + +async def test(): + app = await init_app() + runner = web.AppRunner(app) + await runner.setup() + site = web.TCPSite(runner, 'localhost', 8080) + print('Starting server in maintenance mode at http://localhost:8080') + await site.start() + print('Serving static files from build/web/ directory') + print('You can open http://localhost:8080 in your browser now') + print('Server will shut down in 10 seconds...') + for i in range(10, 0, -1): + print(f'{i}...') + await asyncio.sleep(1) + print('Shutting down server') + await runner.cleanup() + +if __name__ == '__main__': + asyncio.run(test()) +" \ No newline at end of file diff --git a/web/favicon.png b/web/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..789da555426dce34be1aa66386f39b9482f7284c --- /dev/null +++ b/web/favicon.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7ab2525f4b86b65d3e4c70358a17e5a1aaf6f437f99cbcc046dad73d59bb9015 +size 917 diff --git a/web/icons/Icon-192.png b/web/icons/Icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..ea18c9895f624b678c5bfd164be1a56d1d7337e8 --- /dev/null +++ b/web/icons/Icon-192.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3dce99077602f70421c1c6b2a240bc9b83d64d86681d45f2154143310c980be3 +size 5292 diff --git a/web/icons/Icon-512.png b/web/icons/Icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..c2bd3920740c4533560930a1c552015c69efef2a --- /dev/null +++ b/web/icons/Icon-512.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:baccb205ae45f0b421be1657259b4943ac40c95094ab877f3bcbe12cd544dcbe +size 8252 diff --git a/web/icons/Icon-maskable-192.png b/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000000000000000000000000000000000000..9c63e0c4894ad2ddba48d332b04e0f0a713547c1 --- /dev/null +++ b/web/icons/Icon-maskable-192.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d2c842e22a9f4ec9d996b23373a905c88d9a203b220c5c151885ad621f974b5c +size 5594 diff --git a/web/icons/Icon-maskable-512.png b/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000000000000000000000000000000000000..2e7131f2098587f151de196b25656e2e303a14e1 --- /dev/null +++ b/web/icons/Icon-maskable-512.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6aee06cdcab6b2aef74b1734c4778f4421d2da100b0ff9e52b21b55240202929 +size 20998 diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000000000000000000000000000000000000..35db0fee713f3d7a031faf1fe1c61c1ee8d1381d --- /dev/null +++ b/web/index.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + aitube2 + + + + + + diff --git a/web/manifest.json b/web/manifest.json new file mode 100644 index 0000000000000000000000000000000000000000..d3dcff33c98515d6ccf89de7e3f60479b0c88ea6 --- /dev/null +++ b/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "aitube2", + "short_name": "aitube2", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/windows/.gitignore b/windows/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..d492d0d98c8fdc9f93ad2543bb4f531803e9df72 --- /dev/null +++ b/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..ead608b910888bdd7ca75681ac49af5beb59b114 --- /dev/null +++ b/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(aitube2 LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "aitube2") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/windows/flutter/CMakeLists.txt b/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..903f4899d6fced0eb941e159f7322b21b320c40c --- /dev/null +++ b/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000000000000000000000000000000000000..2048c4552885b77f4778e1807dfc4ac094bc7a16 --- /dev/null +++ b/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,17 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + FlutterSecureStorageWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); +} diff --git a/windows/flutter/generated_plugin_registrant.h b/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000000000000000000000000000000000000..dc139d85a93101cc0f6e9db03a3e1a9f68e8dd7e --- /dev/null +++ b/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000000000000000000000000000000000000..de626cc89cd78d54d81130f592ec2659fa77293f --- /dev/null +++ b/windows/flutter/generated_plugins.cmake @@ -0,0 +1,25 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + flutter_secure_storage_windows + url_launcher_windows +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/windows/runner/CMakeLists.txt b/windows/runner/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..394917c053a04daa76d8b94f0a02db0bd99e1034 --- /dev/null +++ b/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/windows/runner/Runner.rc b/windows/runner/Runner.rc new file mode 100644 index 0000000000000000000000000000000000000000..0b8fa39a3f22c24c550dd69212c4dbf7c000d018 --- /dev/null +++ b/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "aitube2" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "aitube2" "\0" + VALUE "LegalCopyright", "Copyright (C) 2024 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "aitube2.exe" "\0" + VALUE "ProductName", "aitube2" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/windows/runner/flutter_window.cpp b/windows/runner/flutter_window.cpp new file mode 100644 index 0000000000000000000000000000000000000000..955ee3038f988929fac2dedb6a307ebd45dfcfd7 --- /dev/null +++ b/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/windows/runner/flutter_window.h b/windows/runner/flutter_window.h new file mode 100644 index 0000000000000000000000000000000000000000..6da0652f05f28fc6950cec20ebdfbae89b665479 --- /dev/null +++ b/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/windows/runner/main.cpp b/windows/runner/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9c85eff22d9aadffa194b498a55f644d98004f4e --- /dev/null +++ b/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"aitube2", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/windows/runner/resource.h b/windows/runner/resource.h new file mode 100644 index 0000000000000000000000000000000000000000..66a65d1e4a79f230031ec0e0959a721039e7282f --- /dev/null +++ b/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/windows/runner/resources/app_icon.ico b/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..4001f142685fdc69eb4ea59fb389f6c29d5f9c14 --- /dev/null +++ b/windows/runner/resources/app_icon.ico @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c098d3fc85cacff98b8e69811b48e9f0d852fcee278132d794411d978869cbf8 +size 33772 diff --git a/windows/runner/runner.exe.manifest b/windows/runner/runner.exe.manifest new file mode 100644 index 0000000000000000000000000000000000000000..153653e8d67f81b901d28257a65908f3278e058d --- /dev/null +++ b/windows/runner/runner.exe.manifest @@ -0,0 +1,14 @@ + + + + + PerMonitorV2 + + + + + + + + + diff --git a/windows/runner/utils.cpp b/windows/runner/utils.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3a0b46511a71b464dac72d693faae6ea0f7e3b2e --- /dev/null +++ b/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + unsigned int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/windows/runner/utils.h b/windows/runner/utils.h new file mode 100644 index 0000000000000000000000000000000000000000..3879d54755798567f0f318d63340508d5668eb96 --- /dev/null +++ b/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/windows/runner/win32_window.cpp b/windows/runner/win32_window.cpp new file mode 100644 index 0000000000000000000000000000000000000000..60608d0fe5bff3a7a7e05d5a232944a1a8effe5f --- /dev/null +++ b/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/windows/runner/win32_window.h b/windows/runner/win32_window.h new file mode 100644 index 0000000000000000000000000000000000000000..e901dde684ea8ba25c50ea5dc7d5900da31a30d4 --- /dev/null +++ b/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_