jbilcke-hf HF Staff commited on
Commit
5acd9c3
·
0 Parent(s):

initial commit log 🪵🦫

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +9 -0
  2. .gitignore +53 -0
  3. .metadata +45 -0
  4. DEPLOYMENT.md +73 -0
  5. Dockerfile +52 -0
  6. NOTES.md +0 -0
  7. README.md +48 -0
  8. analysis_options.yaml +28 -0
  9. android/.gitignore +13 -0
  10. android/app/build.gradle +44 -0
  11. android/app/src/debug/AndroidManifest.xml +7 -0
  12. android/app/src/main/AndroidManifest.xml +45 -0
  13. android/app/src/main/kotlin/com/example/aitube2/MainActivity.kt +5 -0
  14. android/app/src/main/res/drawable-v21/launch_background.xml +12 -0
  15. android/app/src/main/res/drawable/launch_background.xml +12 -0
  16. android/app/src/main/res/mipmap-hdpi/ic_launcher.png +3 -0
  17. android/app/src/main/res/mipmap-mdpi/ic_launcher.png +3 -0
  18. android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +3 -0
  19. android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +3 -0
  20. android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +3 -0
  21. android/app/src/main/res/values-night/styles.xml +18 -0
  22. android/app/src/main/res/values/styles.xml +18 -0
  23. android/app/src/profile/AndroidManifest.xml +7 -0
  24. android/build.gradle +18 -0
  25. android/gradle.properties +3 -0
  26. android/gradle/wrapper/gradle-wrapper.properties +5 -0
  27. android/settings.gradle +25 -0
  28. api.py +445 -0
  29. api_config.py +184 -0
  30. api_core.py +746 -0
  31. docs/for-bots/flutter-videos/fvp.md +223 -0
  32. docs/for-bots/flutter-videos/fvp_usage_example__main.dart +369 -0
  33. docs/for-bots/flutter-videos/fvp_usage_example__multi_textures.dart +116 -0
  34. docs/for-bots/flutter-videos/video_player.md +145 -0
  35. docs/for-bots/huggingface/hf-api.md +0 -0
  36. ios/.gitignore +34 -0
  37. ios/Flutter/AppFrameworkInfo.plist +26 -0
  38. ios/Flutter/Debug.xcconfig +2 -0
  39. ios/Flutter/Release.xcconfig +2 -0
  40. ios/Podfile +44 -0
  41. ios/Runner.xcodeproj/project.pbxproj +616 -0
  42. ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  43. ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  44. ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
  45. ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +98 -0
  46. ios/Runner.xcworkspace/contents.xcworkspacedata +7 -0
  47. ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  48. ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
  49. ios/Runner/AppDelegate.swift +13 -0
  50. ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +122 -0
.gitattributes ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ *.png filter=lfs diff=lfs merge=lfs -text
2
+ *.zip filter=lfs diff=lfs merge=lfs -text
3
+ *.mp3 filter=lfs diff=lfs merge=lfs -text
4
+ *.mp4 filter=lfs diff=lfs merge=lfs -text
5
+ *.jpeg filter=lfs diff=lfs merge=lfs -text
6
+ *.jpg filter=lfs diff=lfs merge=lfs -text
7
+ *.wav filter=lfs diff=lfs merge=lfs -text
8
+ *.ico filter=lfs diff=lfs merge=lfs -text
9
+ *.wasm filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # secret codes
2
+ lib/config/secrets.dart
3
+ run_locally_with_secrets.sh
4
+
5
+ # Python venv
6
+ .python_venv/
7
+
8
+ # Miscellaneous
9
+ *.class
10
+ *.log
11
+ *.pyc
12
+ *.swp
13
+ .DS_Store
14
+ .atom/
15
+ .buildlog/
16
+ .history
17
+ .svn/
18
+ migrate_working_dir/
19
+
20
+ # IntelliJ related
21
+ *.iml
22
+ *.ipr
23
+ *.iws
24
+ .idea/
25
+
26
+ # The .vscode folder contains launch configuration and tasks you configure in
27
+ # VS Code which you may wish to be included in version control, so this line
28
+ # is commented out by default.
29
+ #.vscode/
30
+
31
+ # Flutter/Dart/Pub related
32
+ **/doc/api/
33
+ **/ios/Flutter/.last_build_id
34
+ .dart_tool/
35
+ .flutter-plugins
36
+ .flutter-plugins-dependencies
37
+ .pub-cache/
38
+ .pub/
39
+ /build/
40
+ !/build/web
41
+
42
+ # Symbolication related
43
+ app.*.symbols
44
+
45
+ # Obfuscation related
46
+ app.*.map.json
47
+
48
+ # Android Studio will place build artifacts here
49
+ /android/app/debug
50
+ /android/app/profile
51
+ /android/app/release
52
+
53
+ /assets/config/
.metadata ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This file tracks properties of this Flutter project.
2
+ # Used by Flutter tool to assess capabilities and perform upgrades etc.
3
+ #
4
+ # This file should be version controlled and should not be manually edited.
5
+
6
+ version:
7
+ revision: "dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668"
8
+ channel: "stable"
9
+
10
+ project_type: app
11
+
12
+ # Tracks metadata for the flutter migrate command
13
+ migration:
14
+ platforms:
15
+ - platform: root
16
+ create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
17
+ base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
18
+ - platform: android
19
+ create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
20
+ base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
21
+ - platform: ios
22
+ create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
23
+ base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
24
+ - platform: linux
25
+ create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
26
+ base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
27
+ - platform: macos
28
+ create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
29
+ base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
30
+ - platform: web
31
+ create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
32
+ base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
33
+ - platform: windows
34
+ create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
35
+ base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668
36
+
37
+ # User provided section
38
+
39
+ # List of Local paths (relative to this file) that should be
40
+ # ignored by the migrate tool.
41
+ #
42
+ # Files that are not part of the templates will be ignored by default.
43
+ unmanaged_files:
44
+ - 'lib/main.dart'
45
+ - 'ios/Runner.xcodeproj/project.pbxproj'
DEPLOYMENT.md ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ ## Deploying aitube2 to https://aitube.at
3
+
4
+ Note: this document is meant for aitube administrators only, not the general public.
5
+
6
+ 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).
7
+
8
+ ### Setup the domain
9
+
10
+ TODO
11
+
12
+ ### Seting up the Python virtual environment
13
+
14
+ ```bash
15
+ python3 -m venv .python_venv
16
+ source .python_venv/bin/activate
17
+ python3 -m pip install --no-cache-dir --upgrade -r requirements.txt
18
+ ```
19
+
20
+ ### Deployment to production
21
+
22
+ To deploy the aitube2 api to production:
23
+
24
+ $ git push space main
25
+
26
+ To deploy the aitube2 client to production, simply run:
27
+
28
+ $ flutter run web
29
+
30
+ and upload the assets to:
31
+
32
+ https://huggingface.co/spaces/jbilcke-hf/aitube2/tree/main/public
33
+
34
+ #### Running a rendering node
35
+
36
+ Current aitube uses `jbilcke-hf/LTX-Video-0-9-6-HFIE` as a rendering node.
37
+
38
+ aitube uses a round-robin schedule implemented on the gateway.
39
+ This helps ensuring a smooth attribution of requests.
40
+
41
+ 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.
42
+
43
+ ```bash
44
+ # note: you need to replace <YOUR_ACCOUNT_NAME>, <ROUND_ROBIN_INDEX> and <YOUR_HF_TOKEN>
45
+
46
+ curl https://api.endpoints.huggingface.cloud/v2/endpoint/<YOUR_ACCOUNT_NAME> -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-<ROUND_ROBIN_INDEX>","provider":{"region":"us-east-1","vendor":"aws"},"tags":[""],"type":"protected"}' -H "Content-Type: application/json" -H "Authorization: Bearer <YOUR_HF_TOKEN>"
47
+ ```
48
+
49
+ #### Running the gateway scheduler
50
+
51
+ ```bash
52
+ # load the environment
53
+ # (if you haven't done it already for this shell session)
54
+ source .python_venv/bin/activate
55
+
56
+ HF_TOKEN="<USE YOUR OWN TOKEN>" \
57
+ SECRET_TOKEN="<USE YOUR OWN TOKEN>" \
58
+ VIDEO_ROUND_ROBIN_SERVER_1="https:/<USE YOUR OWN SERVER>.endpoints.huggingface.cloud" \
59
+ VIDEO_ROUND_ROBIN_SERVER_2="https://<USE YOUR OWN SERVER>.endpoints.huggingface.cloud" \
60
+ VIDEO_ROUND_ROBIN_SERVER_3="https://<USE YOUR OWN SERVER>.endpoints.huggingface.cloud" \
61
+ VIDEO_ROUND_ROBIN_SERVER_4="https://<USE YOUR OWN SERVER>.endpoints.huggingface.cloud" \
62
+ HF_IMAGE_MODEL="https://<USE YOUR OWN SERVER>.endpoints.huggingface.cloud" \
63
+ HF_TEXT_MODEL="https://<USE YOUR OWN SERVER>.endpoints.huggingface.cloud" \
64
+ python3 api.py
65
+ ```
66
+
67
+ ### Run the client (web)
68
+
69
+ ```bash
70
+
71
+ flutter run --dart-define=CONFIG_PATH=assets/config/aitube_low.yaml -d chrome
72
+ ```
73
+
Dockerfile ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM nvidia/cuda:12.4.0-devel-ubuntu22.04
2
+
3
+ ARG DEBIAN_FRONTEND=noninteractive
4
+
5
+ ENV PYTHONUNBUFFERED=1
6
+
7
+ RUN apt-get update && apt-get install --no-install-recommends -y \
8
+ build-essential \
9
+ python3.11 \
10
+ python3-pip \
11
+ python3-dev \
12
+ git \
13
+ curl \
14
+ ffmpeg \
15
+ libglib2.0-0 \
16
+ libsm6 \
17
+ libxrender1 \
18
+ libxext6 \
19
+ && apt-get clean && rm -rf /var/lib/apt/lists/*
20
+
21
+ WORKDIR /code
22
+
23
+ COPY ./requirements.txt /code/requirements.txt
24
+
25
+ # Set up a new user named "user" with user ID 1000
26
+ RUN useradd -m -u 1000 user
27
+ # Switch to the "user" user
28
+ USER user
29
+ # Set home to the user's home directory
30
+ ENV HOME=/home/user \
31
+ PATH=/home/user/.local/bin:$PATH
32
+
33
+ # Set home to the user's home directory
34
+ ENV PYTHONPATH=$HOME/app \
35
+ PYTHONUNBUFFERED=1 \
36
+ DATA_ROOT=/tmp/data
37
+
38
+ RUN echo "Installing requirements.txt"
39
+ RUN pip3 install --no-cache-dir --upgrade -r /code/requirements.txt
40
+
41
+ # yeah.. this is manual for now
42
+ #RUN flutter build web
43
+
44
+ WORKDIR $HOME/app
45
+
46
+ COPY --chown=user . $HOME/app
47
+
48
+ EXPOSE 8080
49
+
50
+ ENV PORT 8080
51
+
52
+ CMD python3 api.py
NOTES.md ADDED
File without changes
README.md ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: aitube2
3
+ emoji: 🎥
4
+ colorFrom: red
5
+ colorTo: yellow
6
+ sdk: docker
7
+ app_file: api.py
8
+ pinned: true
9
+ short_description: A Latent YouTube
10
+ ---
11
+
12
+
13
+ # AiTube2
14
+
15
+ ## News
16
+
17
+ aitube2 is coming sooner than expected!
18
+
19
+ Stay hooked at @flngr on X!
20
+
21
+
22
+ ## What is AiTube?
23
+
24
+ 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.
25
+
26
+ 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).
27
+
28
+ This allows for new ways of consuming AI generated content, such as collaborative and interactive prompting.
29
+
30
+ # Where can I use it?
31
+
32
+ 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.
33
+
34
+ # Why can't I use it?
35
+
36
+ As this is a personal project I only have limited ressources to develop it on the side, but there are also technological bottlenecks.
37
+
38
+ 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.
39
+
40
+ 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.
41
+
42
+ # When can I use it?
43
+
44
+ I estimate it will take up to 1 to 2 years for more powerful and/or cheaper hardware to become available.
45
+
46
+ 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).
47
+
48
+ 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).
analysis_options.yaml ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This file configures the analyzer, which statically analyzes Dart code to
2
+ # check for errors, warnings, and lints.
3
+ #
4
+ # The issues identified by the analyzer are surfaced in the UI of Dart-enabled
5
+ # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
6
+ # invoked from the command line by running `flutter analyze`.
7
+
8
+ # The following line activates a set of recommended lints for Flutter apps,
9
+ # packages, and plugins designed to encourage good coding practices.
10
+ include: package:flutter_lints/flutter.yaml
11
+
12
+ linter:
13
+ # The lint rules applied to this project can be customized in the
14
+ # section below to disable rules from the `package:flutter_lints/flutter.yaml`
15
+ # included above or to enable additional rules. A list of all available lints
16
+ # and their documentation is published at https://dart.dev/lints.
17
+ #
18
+ # Instead of disabling a lint rule for the entire project in the
19
+ # section below, it can also be suppressed for a single line of code
20
+ # or a specific dart file by using the `// ignore: name_of_lint` and
21
+ # `// ignore_for_file: name_of_lint` syntax on the line or in the file
22
+ # producing the lint.
23
+ rules:
24
+ # avoid_print: false # Uncomment to disable the `avoid_print` rule
25
+ # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
26
+
27
+ # Additional information about this file can be found at
28
+ # https://dart.dev/guides/language/analysis-options
android/.gitignore ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ gradle-wrapper.jar
2
+ /.gradle
3
+ /captures/
4
+ /gradlew
5
+ /gradlew.bat
6
+ /local.properties
7
+ GeneratedPluginRegistrant.java
8
+
9
+ # Remember to never publicly share your keystore.
10
+ # See https://flutter.dev/to/reference-keystore
11
+ key.properties
12
+ **/*.keystore
13
+ **/*.jks
android/app/build.gradle ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ plugins {
2
+ id "com.android.application"
3
+ id "kotlin-android"
4
+ // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
5
+ id "dev.flutter.flutter-gradle-plugin"
6
+ }
7
+
8
+ android {
9
+ namespace = "com.example.aitube2"
10
+ compileSdk = flutter.compileSdkVersion
11
+ ndkVersion = flutter.ndkVersion
12
+
13
+ compileOptions {
14
+ sourceCompatibility = JavaVersion.VERSION_1_8
15
+ targetCompatibility = JavaVersion.VERSION_1_8
16
+ }
17
+
18
+ kotlinOptions {
19
+ jvmTarget = JavaVersion.VERSION_1_8
20
+ }
21
+
22
+ defaultConfig {
23
+ // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
24
+ applicationId = "com.example.aitube2"
25
+ // You can update the following values to match your application needs.
26
+ // For more information, see: https://flutter.dev/to/review-gradle-config.
27
+ minSdk = flutter.minSdkVersion
28
+ targetSdk = flutter.targetSdkVersion
29
+ versionCode = flutter.versionCode
30
+ versionName = flutter.versionName
31
+ }
32
+
33
+ buildTypes {
34
+ release {
35
+ // TODO: Add your own signing config for the release build.
36
+ // Signing with the debug keys for now, so `flutter run --release` works.
37
+ signingConfig = signingConfigs.debug
38
+ }
39
+ }
40
+ }
41
+
42
+ flutter {
43
+ source = "../.."
44
+ }
android/app/src/debug/AndroidManifest.xml ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ <!-- The INTERNET permission is required for development. Specifically,
3
+ the Flutter tool needs it to communicate with the running application
4
+ to allow setting breakpoints, to provide hot reload, etc.
5
+ -->
6
+ <uses-permission android:name="android.permission.INTERNET"/>
7
+ </manifest>
android/app/src/main/AndroidManifest.xml ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ <application
3
+ android:label="aitube2"
4
+ android:name="${applicationName}"
5
+ android:icon="@mipmap/ic_launcher">
6
+ <activity
7
+ android:name=".MainActivity"
8
+ android:exported="true"
9
+ android:launchMode="singleTop"
10
+ android:taskAffinity=""
11
+ android:theme="@style/LaunchTheme"
12
+ android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
13
+ android:hardwareAccelerated="true"
14
+ android:windowSoftInputMode="adjustResize">
15
+ <!-- Specifies an Android theme to apply to this Activity as soon as
16
+ the Android process has started. This theme is visible to the user
17
+ while the Flutter UI initializes. After that, this theme continues
18
+ to determine the Window background behind the Flutter UI. -->
19
+ <meta-data
20
+ android:name="io.flutter.embedding.android.NormalTheme"
21
+ android:resource="@style/NormalTheme"
22
+ />
23
+ <intent-filter>
24
+ <action android:name="android.intent.action.MAIN"/>
25
+ <category android:name="android.intent.category.LAUNCHER"/>
26
+ </intent-filter>
27
+ </activity>
28
+ <!-- Don't delete the meta-data below.
29
+ This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
30
+ <meta-data
31
+ android:name="flutterEmbedding"
32
+ android:value="2" />
33
+ </application>
34
+ <!-- Required to query activities that can process text, see:
35
+ https://developer.android.com/training/package-visibility and
36
+ https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
37
+
38
+ In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
39
+ <queries>
40
+ <intent>
41
+ <action android:name="android.intent.action.PROCESS_TEXT"/>
42
+ <data android:mimeType="text/plain"/>
43
+ </intent>
44
+ </queries>
45
+ </manifest>
android/app/src/main/kotlin/com/example/aitube2/MainActivity.kt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ package com.example.aitube2
2
+
3
+ import io.flutter.embedding.android.FlutterActivity
4
+
5
+ class MainActivity: FlutterActivity()
android/app/src/main/res/drawable-v21/launch_background.xml ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!-- Modify this file to customize your launch splash screen -->
3
+ <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
4
+ <item android:drawable="?android:colorBackground" />
5
+
6
+ <!-- You can insert your own image assets here -->
7
+ <!-- <item>
8
+ <bitmap
9
+ android:gravity="center"
10
+ android:src="@mipmap/launch_image" />
11
+ </item> -->
12
+ </layer-list>
android/app/src/main/res/drawable/launch_background.xml ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!-- Modify this file to customize your launch splash screen -->
3
+ <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
4
+ <item android:drawable="@android:color/white" />
5
+
6
+ <!-- You can insert your own image assets here -->
7
+ <!-- <item>
8
+ <bitmap
9
+ android:gravity="center"
10
+ android:src="@mipmap/launch_image" />
11
+ </item> -->
12
+ </layer-list>
android/app/src/main/res/mipmap-hdpi/ic_launcher.png ADDED

Git LFS Details

  • SHA256: 6a7c8f0d703e3682108f9662f813302236240d3f8f638bb391e32bfb96055fef
  • Pointer size: 128 Bytes
  • Size of remote file: 544 Bytes
android/app/src/main/res/mipmap-mdpi/ic_launcher.png ADDED

Git LFS Details

  • SHA256: c7c0c0189145e4e32a401c61c9bdc615754b0264e7afae24e834bb81049eaf81
  • Pointer size: 128 Bytes
  • Size of remote file: 442 Bytes
android/app/src/main/res/mipmap-xhdpi/ic_launcher.png ADDED

Git LFS Details

  • SHA256: e14aa40904929bf313fded22cf7e7ffcbf1d1aac4263b5ef1be8bfce650397aa
  • Pointer size: 128 Bytes
  • Size of remote file: 721 Bytes
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png ADDED

Git LFS Details

  • SHA256: 4d470bf22d5c17d84edc5f82516d1ba8a1c09559cd761cefb792f86d9f52b540
  • Pointer size: 129 Bytes
  • Size of remote file: 1.03 kB
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png ADDED

Git LFS Details

  • SHA256: 3c34e1f298d0c9ea3455d46db6b7759c8211a49e9ec6e44b635fc5c87dfb4180
  • Pointer size: 129 Bytes
  • Size of remote file: 1.44 kB
android/app/src/main/res/values-night/styles.xml ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <resources>
3
+ <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
4
+ <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
5
+ <!-- Show a splash screen on the activity. Automatically removed when
6
+ the Flutter engine draws its first frame -->
7
+ <item name="android:windowBackground">@drawable/launch_background</item>
8
+ </style>
9
+ <!-- Theme applied to the Android Window as soon as the process has started.
10
+ This theme determines the color of the Android Window while your
11
+ Flutter UI initializes, as well as behind your Flutter UI while its
12
+ running.
13
+
14
+ This Theme is only used starting with V2 of Flutter's Android embedding. -->
15
+ <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
16
+ <item name="android:windowBackground">?android:colorBackground</item>
17
+ </style>
18
+ </resources>
android/app/src/main/res/values/styles.xml ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <resources>
3
+ <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
4
+ <style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
5
+ <!-- Show a splash screen on the activity. Automatically removed when
6
+ the Flutter engine draws its first frame -->
7
+ <item name="android:windowBackground">@drawable/launch_background</item>
8
+ </style>
9
+ <!-- Theme applied to the Android Window as soon as the process has started.
10
+ This theme determines the color of the Android Window while your
11
+ Flutter UI initializes, as well as behind your Flutter UI while its
12
+ running.
13
+
14
+ This Theme is only used starting with V2 of Flutter's Android embedding. -->
15
+ <style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
16
+ <item name="android:windowBackground">?android:colorBackground</item>
17
+ </style>
18
+ </resources>
android/app/src/profile/AndroidManifest.xml ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ <!-- The INTERNET permission is required for development. Specifically,
3
+ the Flutter tool needs it to communicate with the running application
4
+ to allow setting breakpoints, to provide hot reload, etc.
5
+ -->
6
+ <uses-permission android:name="android.permission.INTERNET"/>
7
+ </manifest>
android/build.gradle ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ allprojects {
2
+ repositories {
3
+ google()
4
+ mavenCentral()
5
+ }
6
+ }
7
+
8
+ rootProject.buildDir = "../build"
9
+ subprojects {
10
+ project.buildDir = "${rootProject.buildDir}/${project.name}"
11
+ }
12
+ subprojects {
13
+ project.evaluationDependsOn(":app")
14
+ }
15
+
16
+ tasks.register("clean", Delete) {
17
+ delete rootProject.buildDir
18
+ }
android/gradle.properties ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError
2
+ android.useAndroidX=true
3
+ android.enableJetifier=true
android/gradle/wrapper/gradle-wrapper.properties ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ distributionBase=GRADLE_USER_HOME
2
+ distributionPath=wrapper/dists
3
+ zipStoreBase=GRADLE_USER_HOME
4
+ zipStorePath=wrapper/dists
5
+ distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip
android/settings.gradle ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ pluginManagement {
2
+ def flutterSdkPath = {
3
+ def properties = new Properties()
4
+ file("local.properties").withInputStream { properties.load(it) }
5
+ def flutterSdkPath = properties.getProperty("flutter.sdk")
6
+ assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
7
+ return flutterSdkPath
8
+ }()
9
+
10
+ includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
11
+
12
+ repositories {
13
+ google()
14
+ mavenCentral()
15
+ gradlePluginPortal()
16
+ }
17
+ }
18
+
19
+ plugins {
20
+ id "dev.flutter.flutter-plugin-loader" version "1.0.0"
21
+ id "com.android.application" version "8.1.0" apply false
22
+ id "org.jetbrains.kotlin.android" version "1.8.22" apply false
23
+ }
24
+
25
+ include ":app"
api.py ADDED
@@ -0,0 +1,445 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import json
3
+ import logging
4
+ import os
5
+ import pathlib
6
+ from aiohttp import web, WSMsgType
7
+ from typing import Dict, Any
8
+ from api_core import VideoGenerationAPI
9
+
10
+ from api_config import *
11
+
12
+ # Configure logging
13
+ logging.basicConfig(
14
+ level=logging.INFO,
15
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
16
+ )
17
+ logger = logging.getLogger(__name__)
18
+
19
+ async def process_generic_request(data: dict, ws: web.WebSocketResponse, api) -> None:
20
+ """Handle general requests that don't fit into specialized queues"""
21
+ try:
22
+ request_id = data.get('requestId')
23
+ action = data.get('action')
24
+
25
+ def error_response(message: str):
26
+ return {
27
+ 'action': action,
28
+ 'requestId': request_id,
29
+ 'success': False,
30
+ 'error': message
31
+ }
32
+
33
+ if action == 'heartbeat':
34
+ # Include user role info in heartbeat response
35
+ user_role = getattr(ws, 'user_role', 'anon')
36
+ await ws.send_json({
37
+ 'action': 'heartbeat',
38
+ 'requestId': request_id,
39
+ 'success': True,
40
+ 'user_role': user_role
41
+ })
42
+
43
+ elif action == 'get_user_role':
44
+ # Return the user role information
45
+ user_role = getattr(ws, 'user_role', 'anon')
46
+ await ws.send_json({
47
+ 'action': 'get_user_role',
48
+ 'requestId': request_id,
49
+ 'success': True,
50
+ 'user_role': user_role
51
+ })
52
+
53
+ elif action == 'generate_caption':
54
+ title = data.get('params', {}).get('title')
55
+ description = data.get('params', {}).get('description')
56
+
57
+ if not title or not description:
58
+ await ws.send_json(error_response('Missing title or description'))
59
+ return
60
+
61
+ caption = await api.generate_caption(title, description)
62
+ await ws.send_json({
63
+ 'action': action,
64
+ 'requestId': request_id,
65
+ 'success': True,
66
+ 'caption': caption
67
+ })
68
+
69
+ elif action == 'generate_thumbnail':
70
+ title = data.get('params', {}).get('title')
71
+ description = data.get('params', {}).get('description')
72
+
73
+ if not title or not description:
74
+ await ws.send_json(error_response('Missing title or description'))
75
+ return
76
+
77
+ thumbnail = await api.generate_thumbnail(title, description)
78
+ await ws.send_json({
79
+ 'action': action,
80
+ 'requestId': request_id,
81
+ 'success': True,
82
+ 'thumbnailUrl': thumbnail
83
+ })
84
+
85
+ else:
86
+ await ws.send_json(error_response(f'Unknown action: {action}'))
87
+
88
+ except Exception as e:
89
+ logger.error(f"Error processing generic request: {str(e)}")
90
+ try:
91
+ await ws.send_json({
92
+ 'action': data.get('action'),
93
+ 'requestId': data.get('requestId'),
94
+ 'success': False,
95
+ 'error': f'Internal server error: {str(e)}'
96
+ })
97
+ except Exception as send_error:
98
+ logger.error(f"Error sending error response: {send_error}")
99
+
100
+ async def process_search_queue(queue: asyncio.Queue, ws: web.WebSocketResponse, api):
101
+ """Medium priority queue for search operations"""
102
+ while True:
103
+ try:
104
+ data = await queue.get()
105
+ request_id = data.get('requestId')
106
+ query = data.get('query', '').strip()
107
+ search_count = data.get('searchCount', 0)
108
+ attempt_count = data.get('attemptCount', 0)
109
+
110
+ logger.info(f"Processing search request: query='{query}', search_count={search_count}, attempt={attempt_count}")
111
+
112
+ if not query:
113
+ logger.warning(f"Empty query received in request: {data}")
114
+ result = {
115
+ 'action': 'search',
116
+ 'requestId': request_id,
117
+ 'success': False,
118
+ 'error': 'No search query provided'
119
+ }
120
+ else:
121
+ try:
122
+ search_result = await api.search_video(
123
+ query,
124
+ search_count=search_count,
125
+ attempt_count=attempt_count
126
+ )
127
+
128
+ if search_result:
129
+ logger.info(f"Search successful for query '{query}' (#{search_count})")
130
+ result = {
131
+ 'action': 'search',
132
+ 'requestId': request_id,
133
+ 'success': True,
134
+ 'result': search_result
135
+ }
136
+ else:
137
+ logger.warning(f"No results found for query '{query}' (#{search_count})")
138
+ result = {
139
+ 'action': 'search',
140
+ 'requestId': request_id,
141
+ 'success': False,
142
+ 'error': 'No results found'
143
+ }
144
+ except Exception as e:
145
+ logger.error(f"Search error for query '{query}' (#{search_count}, attempt {attempt_count}): {str(e)}")
146
+ result = {
147
+ 'action': 'search',
148
+ 'requestId': request_id,
149
+ 'success': False,
150
+ 'error': f'Search error: {str(e)}'
151
+ }
152
+
153
+ await ws.send_json(result)
154
+
155
+ except Exception as e:
156
+ logger.error(f"Error in search queue processor: {str(e)}")
157
+ try:
158
+ error_response = {
159
+ 'action': 'search',
160
+ 'requestId': data.get('requestId') if 'data' in locals() else None,
161
+ 'success': False,
162
+ 'error': f'Internal server error: {str(e)}'
163
+ }
164
+ await ws.send_json(error_response)
165
+ except Exception as send_error:
166
+ logger.error(f"Error sending error response: {send_error}")
167
+ finally:
168
+ if 'queue' in locals():
169
+ queue.task_done()
170
+
171
+ async def process_chat_queue(queue: asyncio.Queue, ws: web.WebSocketResponse):
172
+ """High priority queue for chat operations"""
173
+ while True:
174
+ data = await queue.get()
175
+ try:
176
+ api = ws.app['api']
177
+ if data['action'] == 'join_chat':
178
+ result = await api.handle_join_chat(data, ws)
179
+ elif data['action'] == 'chat_message':
180
+ result = await api.handle_chat_message(data, ws)
181
+ elif data['action'] == 'leave_chat':
182
+ result = await api.handle_leave_chat(data, ws)
183
+ await ws.send_json(result)
184
+ except Exception as e:
185
+ logger.error(f"Error processing chat request: {e}")
186
+ try:
187
+ await ws.send_json({
188
+ 'action': data['action'],
189
+ 'requestId': data.get('requestId'),
190
+ 'success': False,
191
+ 'error': f'Chat error: {str(e)}'
192
+ })
193
+ except Exception as send_error:
194
+ logger.error(f"Error sending error response: {send_error}")
195
+ finally:
196
+ queue.task_done()
197
+
198
+ async def process_video_queue(queue: asyncio.Queue, ws: web.WebSocketResponse):
199
+ """Process multiple video generation requests in parallel"""
200
+ active_tasks = set()
201
+ MAX_CONCURRENT = len(VIDEO_ROUND_ROBIN_ENDPOINT_URLS) # Match client's max concurrent generations
202
+
203
+ async def process_single_request(data):
204
+ try:
205
+ api = ws.app['api']
206
+ title = data.get('title', '')
207
+ description = data.get('description', '')
208
+ video_prompt_prefix = data.get('video_prompt_prefix', '')
209
+ options = data.get('options', {})
210
+
211
+ # Get the user role from the websocket
212
+ user_role = getattr(ws, 'user_role', 'anon')
213
+
214
+ # Pass the user role to generate_video
215
+ video_data = await api.generate_video(title, description, video_prompt_prefix, options, user_role)
216
+
217
+ result = {
218
+ 'action': 'generate_video',
219
+ 'requestId': data.get('requestId'),
220
+ 'success': True,
221
+ 'video': video_data,
222
+ }
223
+
224
+ await ws.send_json(result)
225
+
226
+ except Exception as e:
227
+ logger.error(f"Error processing video request: {e}")
228
+ try:
229
+ await ws.send_json({
230
+ 'action': 'generate_video',
231
+ 'requestId': data.get('requestId'),
232
+ 'success': False,
233
+ 'error': f'Video generation error: {str(e)}'
234
+ })
235
+ except Exception as send_error:
236
+ logger.error(f"Error sending error response: {send_error}")
237
+ finally:
238
+ active_tasks.discard(asyncio.current_task())
239
+
240
+ while True:
241
+ # Clean up completed tasks
242
+ active_tasks = {task for task in active_tasks if not task.done()}
243
+
244
+ # Start new tasks if we have capacity
245
+ while len(active_tasks) < MAX_CONCURRENT:
246
+ try:
247
+ # Use try_get to avoid blocking if queue is empty
248
+ data = await asyncio.wait_for(queue.get(), timeout=0.1)
249
+
250
+ # Create and start new task
251
+ task = asyncio.create_task(process_single_request(data))
252
+ active_tasks.add(task)
253
+
254
+ except asyncio.TimeoutError:
255
+ # No items in queue, break inner loop
256
+ break
257
+ except Exception as e:
258
+ logger.error(f"Error creating video generation task: {e}")
259
+ break
260
+
261
+ # Wait a short time before checking queue again
262
+ await asyncio.sleep(0.1)
263
+
264
+ # Handle any completed tasks' errors
265
+ for task in list(active_tasks):
266
+ if task.done():
267
+ try:
268
+ await task
269
+ except Exception as e:
270
+ logger.error(f"Task failed with error: {e}")
271
+ active_tasks.discard(task)
272
+
273
+ async def status_handler(request: web.Request) -> web.Response:
274
+ """Handler for API status endpoint"""
275
+ api = request.app['api']
276
+ return web.json_response({
277
+ 'product': PRODUCT_NAME,
278
+ 'version': '0.1.0',
279
+ 'maintenance_mode': MAINTENANCE_MODE,
280
+ 'available_endpoints': len(VIDEO_ROUND_ROBIN_ENDPOINT_URLS)
281
+ })
282
+
283
+ async def websocket_handler(request: web.Request) -> web.WebSocketResponse:
284
+ # Check if maintenance mode is enabled
285
+ if MAINTENANCE_MODE:
286
+ # Return an error response indicating maintenance mode
287
+ return web.json_response({
288
+ 'error': 'Server is in maintenance mode',
289
+ 'maintenance': True
290
+ }, status=503) # 503 Service Unavailable
291
+
292
+ ws = web.WebSocketResponse(
293
+ max_msg_size=1024*1024*10, # 10MB max message size
294
+ timeout=30.0 # we want to keep things tight and short
295
+ )
296
+
297
+ ws.app = request.app
298
+ await ws.prepare(request)
299
+ api = request.app['api']
300
+
301
+ # Get the Hugging Face token from query parameters
302
+ hf_token = request.query.get('hf_token', '')
303
+
304
+ # Validate the token and determine the user role
305
+ user_role = await api.validate_user_token(hf_token)
306
+ logger.info(f"User connected with role: {user_role}")
307
+
308
+ # Store the user role in the websocket
309
+ ws.user_role = user_role
310
+
311
+ # Create separate queues for different request types
312
+ chat_queue = asyncio.Queue()
313
+ video_queue = asyncio.Queue()
314
+ search_queue = asyncio.Queue()
315
+
316
+ # Start background tasks for handling different request types
317
+ background_tasks = [
318
+ asyncio.create_task(process_chat_queue(chat_queue, ws)),
319
+ asyncio.create_task(process_video_queue(video_queue, ws)),
320
+ asyncio.create_task(process_search_queue(search_queue, ws, api))
321
+ ]
322
+
323
+ try:
324
+ async for msg in ws:
325
+ if msg.type == WSMsgType.TEXT:
326
+ try:
327
+ data = json.loads(msg.data)
328
+ action = data.get('action')
329
+
330
+ # Route requests to appropriate queues
331
+ if action in ['join_chat', 'leave_chat', 'chat_message']:
332
+ await chat_queue.put(data)
333
+ elif action in ['generate_video']:
334
+ await video_queue.put(data)
335
+ elif action == 'search':
336
+ await search_queue.put(data)
337
+ else:
338
+ await process_generic_request(data, ws, api)
339
+
340
+ except Exception as e:
341
+ logger.error(f"Error processing WebSocket message: {str(e)}")
342
+ await ws.send_json({
343
+ 'action': data.get('action') if 'data' in locals() else 'unknown',
344
+ 'success': False,
345
+ 'error': f'Error processing message: {str(e)}'
346
+ })
347
+
348
+ elif msg.type in (WSMsgType.ERROR, WSMsgType.CLOSE):
349
+ break
350
+
351
+ finally:
352
+ # Cleanup
353
+ for task in background_tasks:
354
+ task.cancel()
355
+ try:
356
+ await asyncio.gather(*background_tasks, return_exceptions=True)
357
+ except asyncio.CancelledError:
358
+ pass
359
+
360
+ return ws
361
+
362
+ async def init_app() -> web.Application:
363
+ app = web.Application(
364
+ client_max_size=1024**2*10 # 10MB max size
365
+ )
366
+
367
+ # Create API instance
368
+ api = VideoGenerationAPI()
369
+ app['api'] = api
370
+
371
+ # Add cleanup logic
372
+ async def cleanup_api(app):
373
+ # Add any necessary cleanup for the API
374
+ pass
375
+
376
+ app.on_shutdown.append(cleanup_api)
377
+
378
+ # Add routes
379
+ app.router.add_get('/ws', websocket_handler)
380
+ app.router.add_get('/api/status', status_handler)
381
+
382
+ # Set up static file serving
383
+ # Define the path to the public directory
384
+ public_path = pathlib.Path(__file__).parent / 'build' / 'web'
385
+ if not public_path.exists():
386
+ public_path.mkdir(parents=True, exist_ok=True)
387
+
388
+ # Set up static file serving with proper security considerations
389
+ async def static_file_handler(request):
390
+ # Get the path from the request (removing leading /)
391
+ path_parts = request.path.lstrip('/').split('/')
392
+
393
+ # Convert to safe path to prevent path traversal attacks
394
+ safe_path = public_path.joinpath(*path_parts)
395
+
396
+ # Make sure the path is within the public directory (prevent directory traversal)
397
+ try:
398
+ safe_path = safe_path.resolve()
399
+ if not str(safe_path).startswith(str(public_path.resolve())):
400
+ return web.HTTPForbidden(text="Access denied")
401
+ except (ValueError, FileNotFoundError):
402
+ return web.HTTPNotFound()
403
+
404
+ # If path is a directory, look for index.html
405
+ if safe_path.is_dir():
406
+ safe_path = safe_path / 'index.html'
407
+
408
+ # Check if the file exists
409
+ if not safe_path.exists() or not safe_path.is_file():
410
+ # If not found, serve index.html (for SPA routing)
411
+ safe_path = public_path / 'index.html'
412
+ if not safe_path.exists():
413
+ return web.HTTPNotFound()
414
+
415
+ # Determine content type based on file extension
416
+ content_type = 'text/plain'
417
+ ext = safe_path.suffix.lower()
418
+ if ext == '.html':
419
+ content_type = 'text/html'
420
+ elif ext == '.js':
421
+ content_type = 'application/javascript'
422
+ elif ext == '.css':
423
+ content_type = 'text/css'
424
+ elif ext in ('.jpg', '.jpeg'):
425
+ content_type = 'image/jpeg'
426
+ elif ext == '.png':
427
+ content_type = 'image/png'
428
+ elif ext == '.gif':
429
+ content_type = 'image/gif'
430
+ elif ext == '.svg':
431
+ content_type = 'image/svg+xml'
432
+ elif ext == '.json':
433
+ content_type = 'application/json'
434
+
435
+ # Return the file with appropriate headers
436
+ return web.FileResponse(safe_path, headers={'Content-Type': content_type})
437
+
438
+ # Add catch-all route for static files (lower priority than API routes)
439
+ app.router.add_get('/{path:.*}', static_file_handler)
440
+
441
+ return app
442
+
443
+ if __name__ == '__main__':
444
+ app = asyncio.run(init_app())
445
+ web.run_app(app, host='0.0.0.0', port=8080)
api_config.py ADDED
@@ -0,0 +1,184 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ PRODUCT_NAME = os.environ.get('PRODUCT_NAME', 'AiTube')
4
+
5
+ TEXT_MODEL = os.environ.get('HF_TEXT_MODEL',
6
+ #'HuggingFaceH4/zephyr-7b-beta'
7
+ 'HuggingFaceTB/SmolLM2-1.7B-Instruct'
8
+ )
9
+
10
+ IMAGE_MODEL = os.environ.get('HF_IMAGE_MODEL', '')
11
+
12
+ # Environment variable to control maintenance mode
13
+ MAINTENANCE_MODE = os.environ.get('MAINTENANCE_MODE', 'false').lower() in ('true', 'yes', '1', 't')
14
+
15
+ ADMIN_ACCOUNTS = [
16
+ "jbilcke-hf"
17
+ ]
18
+
19
+ RAW_VIDEO_ROUND_ROBIN_ENDPOINT_URLS = [
20
+ os.environ.get('VIDEO_ROUND_ROBIN_SERVER_1', ''),
21
+ os.environ.get('VIDEO_ROUND_ROBIN_SERVER_2', ''),
22
+ os.environ.get('VIDEO_ROUND_ROBIN_SERVER_3', ''),
23
+ os.environ.get('VIDEO_ROUND_ROBIN_SERVER_4', ''),
24
+ ]
25
+
26
+ # Filter out empty strings from the endpoint list
27
+ VIDEO_ROUND_ROBIN_ENDPOINT_URLS = [url for url in RAW_VIDEO_ROUND_ROBIN_ENDPOINT_URLS if url]
28
+
29
+ HF_TOKEN = os.environ.get('HF_TOKEN')
30
+
31
+ # use the same secret token as you used to secure your BASE_SPACE_NAME spaces
32
+ SECRET_TOKEN = os.environ.get('SECRET_TOKEN')
33
+
34
+ # 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"
35
+ NEGATIVE_PROMPT = "low quality, worst quality, deformed, distorted, disfigured, blurry, text, watermark"
36
+
37
+ POSITIVE_PROMPT_SUFFIX = "high quality, cinematic, 4K, intricate details"
38
+
39
+ GUIDANCE_SCALE = 1.0
40
+
41
+ # anonymous users are people browing AiTube2 without being connected
42
+ # this category suffers from regular abuse so we need to enforce strict limitations
43
+ CONFIG_FOR_ANONYMOUS_USERS = {
44
+
45
+ # anons can only watch 2 minutes per video
46
+ "max_rendering_time_per_client_per_video_in_sec": 2 * 60,
47
+
48
+ "max_buffer_size": 2,
49
+ "max_concurrent_generations": 2,
50
+
51
+ "min_num_inference_steps": 2,
52
+ "default_num_inference_steps": 2,
53
+ "max_num_inference_steps": 4,
54
+
55
+ "min_num_frames": 9, # 8 + 1
56
+ "default_max_num_frames": 65, # 8*8 + 1
57
+ "max_num_frames": 65, # 8*8 + 1
58
+
59
+ "min_clip_duration_seconds": 1,
60
+ "default_clip_duration_seconds": 2,
61
+ "max_clip_duration_seconds": 2,
62
+
63
+ "min_clip_playback_speed": 0.5,
64
+ "default_clip_playback_speed": 0.5,
65
+ "max_clip_playback_speed": 0.5,
66
+
67
+ "min_clip_framerate": 8,
68
+ "default_clip_framerate": 16,
69
+ "max_clip_framerate": 16,
70
+
71
+ "min_clip_width": 544,
72
+ "default_clip_width": 544,
73
+ "max_clip_width": 544,
74
+
75
+ "min_clip_height": 320,
76
+ "default_clip_height": 320,
77
+ "max_clip_height": 320,
78
+ }
79
+
80
+ # Hugging Face users enjoy a more normal and calibrated experience
81
+ CONFIG_FOR_STANDARD_HF_USERS = {
82
+ "max_rendering_time_per_client_per_video_in_sec": 15 * 60,
83
+
84
+ "max_buffer_size": 2,
85
+ "max_concurrent_generations": 2,
86
+
87
+ "min_num_inference_steps": 2,
88
+ "default_num_inference_steps": 4,
89
+ "max_num_inference_steps": 6,
90
+
91
+ "min_num_frames": 9, # 8 + 1
92
+ "default_num_frames": 65, # 8*8 + 1
93
+ "max_num_frames": 65, # 8*8 + 1
94
+
95
+ "min_clip_duration_seconds": 1,
96
+ "default_clip_duration_seconds": 2,
97
+ "max_clip_duration_seconds": 2,
98
+
99
+ "min_clip_playback_speed": 0.5,
100
+ "default_clip_playback_speed": 0.7,
101
+ "max_clip_playback_speed": 0.7,
102
+
103
+ "min_clip_framerate": 8,
104
+ "default_clip_framerate": 25,
105
+ "max_clip_framerate": 25,
106
+
107
+ "min_clip_width": 544,
108
+ "default_clip_width": 640,
109
+ "max_clip_width": 640,
110
+
111
+ "min_clip_height": 320,
112
+ "default_clip_height": 416,
113
+ "max_clip_height": 416,
114
+ }
115
+
116
+ # Hugging Face users with a Pro may enjoy an improved experience
117
+ CONFIG_FOR_PRO_HF_USERS = {
118
+ "max_rendering_time_per_client_per_video_in_sec": 20 * 60,
119
+
120
+ "max_buffer_size": 2,
121
+ "max_concurrent_generations": 2,
122
+
123
+ "min_num_inference_steps": 2,
124
+ "max_num_inference_steps": 8,
125
+
126
+ "min_num_frames": 9, # 8 + 1
127
+ "default_num_frames": 97, # (8*12) + 1
128
+ "max_num_frames": 97, # (8*12) + 1
129
+
130
+ "min_clip_duration_seconds": 1,
131
+ "default_clip_duration_seconds": 2,
132
+ "max_clip_duration_seconds": 3,
133
+
134
+ "min_clip_playback_speed": 0.5,
135
+ "default_clip_playback_speed": 0.8,
136
+ "max_clip_playback_speed": 0.8,
137
+
138
+ "min_clip_framerate": 8,
139
+ "default_clip_framerate": 25,
140
+ "max_clip_framerate": 30,
141
+
142
+ "min_clip_width": 544,
143
+ "default_clip_width": 768,
144
+ "max_clip_width": 768,
145
+
146
+ "min_clip_height": 320,
147
+ "default_clip_height": 480,
148
+ "max_clip_height": 480,
149
+ }
150
+
151
+ CONFIG_FOR_ADMIN_HF_USERS = {
152
+ "max_rendering_time_per_client_per_video_in_sec": 60 * 60,
153
+
154
+ "max_buffer_size": 2,
155
+ "max_concurrent_generations": 2,
156
+
157
+ "min_num_inference_steps": 2,
158
+ "default_num_inference_steps": 4,
159
+ "max_num_inference_steps": 8,
160
+
161
+ "min_num_frames": 9, # 8 + 1
162
+ "default_num_frames": 65, # (8 * 8) + 1
163
+ "max_num_frames": 129, # (8 * 16) + 1
164
+
165
+ "min_clip_duration_seconds": 1,
166
+ "default_clip_duration_seconds": 2,
167
+ "max_clip_duration_seconds": 4,
168
+
169
+ "min_clip_playback_speed": 0.5,
170
+ "default_clip_playback_speed": 0.8,
171
+ "max_clip_playback_speed": 0.9,
172
+
173
+ "min_clip_framerate": 8,
174
+ "default_clip_framerate": 30,
175
+ "max_clip_framerate": 60,
176
+
177
+ "min_clip_width": 544,
178
+ "default_clip_width": 768,
179
+ "max_clip_width": 1216,
180
+
181
+ "min_clip_height": 320,
182
+ "default_clip_height": 480,
183
+ "max_clip_height": 704,
184
+ }
api_core.py ADDED
@@ -0,0 +1,746 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import os
3
+ import io
4
+ import re
5
+ import base64
6
+ import uuid
7
+ from typing import Dict, Any, Optional, List, Literal
8
+ from dataclasses import dataclass
9
+ from asyncio import Lock, Queue
10
+ import asyncio
11
+ import time
12
+ import datetime
13
+ from contextlib import asynccontextmanager
14
+ from collections import defaultdict
15
+ from aiohttp import web, ClientSession
16
+ from huggingface_hub import InferenceClient, HfApi
17
+ from gradio_client import Client
18
+ import random
19
+ import yaml
20
+ import json
21
+
22
+ from api_config import *
23
+
24
+ # User role type
25
+ UserRole = Literal['anon', 'normal', 'pro', 'admin']
26
+
27
+ # Configure logging
28
+ logging.basicConfig(
29
+ level=logging.INFO,
30
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
31
+ )
32
+ logger = logging.getLogger(__name__)
33
+
34
+
35
+ def generate_seed():
36
+ """Generate a random positive 32-bit integer seed."""
37
+ return random.randint(0, 2**32 - 1)
38
+
39
+ def sanitize_yaml_response(response_text: str) -> str:
40
+ """
41
+ Sanitize and format AI response into valid YAML.
42
+ Returns properly formatted YAML string.
43
+ """
44
+
45
+ response_text = response_text.split("```")[0]
46
+
47
+ # Remove any markdown code block indicators and YAML document markers
48
+ clean_text = re.sub(r'```yaml|```|---|\.\.\.$', '', response_text.strip())
49
+
50
+ # Split into lines and process each line
51
+ lines = clean_text.split('\n')
52
+ sanitized_lines = []
53
+ current_field = None
54
+
55
+ for line in lines:
56
+ stripped = line.strip()
57
+ if not stripped:
58
+ continue
59
+
60
+ # Handle field starts
61
+ if stripped.startswith('title:') or stripped.startswith('description:'):
62
+ # Ensure proper YAML format with space after colon and proper quoting
63
+ field_name = stripped.split(':', 1)[0]
64
+ field_value = stripped.split(':', 1)[1].strip().strip('"\'')
65
+
66
+ # Quote the value if it contains special characters
67
+ if any(c in field_value for c in ':[]{},&*#?|-<>=!%@`'):
68
+ field_value = f'"{field_value}"'
69
+
70
+ sanitized_lines.append(f"{field_name}: {field_value}")
71
+ current_field = field_name
72
+
73
+ elif stripped.startswith('tags:'):
74
+ sanitized_lines.append('tags:')
75
+ current_field = 'tags'
76
+
77
+ elif stripped.startswith('-') and current_field == 'tags':
78
+ # Process tag values
79
+ tag = stripped[1:].strip().strip('"\'')
80
+ if tag:
81
+ # Clean and format tag
82
+ tag = re.sub(r'[^\x00-\x7F]+', '', tag) # Remove non-ASCII
83
+ tag = re.sub(r'[^a-zA-Z0-9\s-]', '', tag) # Keep only alphanumeric and hyphen
84
+ tag = tag.strip().lower().replace(' ', '-')
85
+ if tag:
86
+ sanitized_lines.append(f" - {tag}")
87
+
88
+ elif current_field in ['title', 'description']:
89
+ # Handle multi-line title/description continuation
90
+ value = stripped.strip('"\'')
91
+ if value:
92
+ # Append to previous line
93
+ prev = sanitized_lines[-1]
94
+ sanitized_lines[-1] = f"{prev} {value}"
95
+
96
+ # Ensure the YAML has all required fields
97
+ required_fields = {'title', 'description', 'tags'}
98
+ found_fields = {line.split(':')[0].strip() for line in sanitized_lines if ':' in line}
99
+
100
+ for field in required_fields - found_fields:
101
+ if field == 'tags':
102
+ sanitized_lines.extend(['tags:', ' - default'])
103
+ else:
104
+ sanitized_lines.append(f'{field}: "No {field} provided"')
105
+
106
+ return '\n'.join(sanitized_lines)
107
+
108
+ @dataclass
109
+ class Endpoint:
110
+ id: int
111
+ url: str
112
+ busy: bool = False
113
+ last_used: float = 0
114
+
115
+ class EndpointManager:
116
+ def __init__(self):
117
+ self.endpoints: List[Endpoint] = []
118
+ self.lock = Lock()
119
+ self.endpoint_queue: Queue[Endpoint] = Queue()
120
+ self.initialize_endpoints()
121
+
122
+ def initialize_endpoints(self):
123
+ """Initialize the list of endpoints"""
124
+ for i, url in enumerate(VIDEO_ROUND_ROBIN_ENDPOINT_URLS):
125
+ endpoint = Endpoint(id=i + 1, url=url)
126
+ self.endpoints.append(endpoint)
127
+ self.endpoint_queue.put_nowait(endpoint)
128
+
129
+ @asynccontextmanager
130
+ async def get_endpoint(self, max_wait_time: int = 10):
131
+ """Get the next available endpoint using a context manager"""
132
+ start_time = time.time()
133
+ endpoint = None
134
+
135
+ try:
136
+ while True:
137
+ if time.time() - start_time > max_wait_time:
138
+ raise TimeoutError(f"Could not acquire an endpoint within {max_wait_time} seconds")
139
+
140
+ try:
141
+ endpoint = self.endpoint_queue.get_nowait()
142
+ async with self.lock:
143
+ if not endpoint.busy:
144
+ endpoint.busy = True
145
+ endpoint.last_used = time.time()
146
+ break
147
+ else:
148
+ await self.endpoint_queue.put(endpoint)
149
+ except asyncio.QueueEmpty:
150
+ await asyncio.sleep(0.5)
151
+ continue
152
+
153
+ yield endpoint
154
+
155
+ finally:
156
+ if endpoint:
157
+ async with self.lock:
158
+ endpoint.busy = False
159
+ endpoint.last_used = time.time()
160
+ await self.endpoint_queue.put(endpoint)
161
+
162
+ class ChatRoom:
163
+ def __init__(self):
164
+ self.messages = []
165
+ self.connected_clients = set()
166
+ self.max_history = 100
167
+
168
+ def add_message(self, message):
169
+ self.messages.append(message)
170
+ if len(self.messages) > self.max_history:
171
+ self.messages.pop(0)
172
+
173
+ def get_recent_messages(self, limit=50):
174
+ return self.messages[-limit:]
175
+
176
+ class VideoGenerationAPI:
177
+ def __init__(self):
178
+ self.inference_client = InferenceClient(token=HF_TOKEN)
179
+ self.hf_api = HfApi(token=HF_TOKEN)
180
+ self.endpoint_manager = EndpointManager()
181
+ self.active_requests: Dict[str, asyncio.Future] = {}
182
+ self.chat_rooms = defaultdict(ChatRoom)
183
+ self.video_events: Dict[str, List[Dict[str, Any]]] = defaultdict(list)
184
+ self.event_history_limit = 50
185
+ # Cache for user roles to avoid repeated API calls
186
+ self.user_role_cache: Dict[str, Dict[str, Any]] = {}
187
+ # Cache expiration time (10 minutes)
188
+ self.cache_expiration = 600
189
+
190
+
191
+ def _add_event(self, video_id: str, event: Dict[str, Any]):
192
+ """Add an event to the video's history and maintain the size limit"""
193
+ events = self.video_events[video_id]
194
+ events.append(event)
195
+ if len(events) > self.event_history_limit:
196
+ events.pop(0)
197
+
198
+ async def validate_user_token(self, token: str) -> UserRole:
199
+ """
200
+ Validates a Hugging Face token and determines the user's role.
201
+
202
+ Returns one of:
203
+ - 'anon': Anonymous user (no token or invalid token)
204
+ - 'normal': Standard Hugging Face user
205
+ - 'pro': Hugging Face Pro user
206
+ - 'admin': Admin user (username in ADMIN_ACCOUNTS)
207
+ """
208
+ # If no token is provided, the user is anonymous
209
+ if not token:
210
+ return 'anon'
211
+
212
+ # Check if we have a cached result for this token
213
+ current_time = time.time()
214
+ if token in self.user_role_cache:
215
+ cached_data = self.user_role_cache[token]
216
+ # If the cache is still valid
217
+ if current_time - cached_data['timestamp'] < self.cache_expiration:
218
+ logger.info(f"Using cached user role: {cached_data['role']}")
219
+ return cached_data['role']
220
+
221
+ # No valid cache, need to check the token with the HF API
222
+ try:
223
+ # Use HF API to validate the token and get user info
224
+ logger.info("Validating Hugging Face token...")
225
+
226
+ # Run in executor to avoid blocking the event loop
227
+ user_info = await asyncio.get_event_loop().run_in_executor(
228
+ None,
229
+ lambda: self.hf_api.whoami(token=token)
230
+ )
231
+
232
+ logger.info(f"Token valid for user: {user_info.name}")
233
+
234
+ # Determine the user role based on the information
235
+ user_role: UserRole
236
+
237
+ # Check if the user is an admin
238
+ if user_info.name in ADMIN_ACCOUNTS:
239
+ user_role = 'admin'
240
+ # Check if the user has a pro account
241
+ elif hasattr(user_info, 'is_pro') and user_info.is_pro:
242
+ user_role = 'pro'
243
+ else:
244
+ user_role = 'normal'
245
+
246
+ # Cache the result
247
+ self.user_role_cache[token] = {
248
+ 'role': user_role,
249
+ 'timestamp': current_time,
250
+ 'username': user_info.name
251
+ }
252
+
253
+ return user_role
254
+
255
+ except Exception as e:
256
+ logger.error(f"Failed to validate Hugging Face token: {str(e)}")
257
+ # If validation fails, the user is treated as anonymous
258
+ return 'anon'
259
+
260
+ async def download_video(self, url: str) -> bytes:
261
+ """Download video file from URL and return bytes"""
262
+ async with ClientSession() as session:
263
+ async with session.get(url) as response:
264
+ if response.status != 200:
265
+ raise Exception(f"Failed to download video: HTTP {response.status}")
266
+ return await response.read()
267
+
268
+ async def search_video(self, query: str, search_count: int = 0, attempt_count: int = 0) -> Optional[dict]:
269
+ """Generate a single search result using HF text generation"""
270
+ prompt = f"""# Instruction
271
+ Your response MUST be a YAML object containing a title, description, and tags, consistent with what we can find on a video sharing platform.
272
+ 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.
273
+ 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.
274
+ 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!
275
+
276
+ # Context
277
+ This is attempt {attempt_count} at generating search result number {search_count}.
278
+
279
+ # Input
280
+ Describe the video for this theme: "{query}".
281
+ Don't forget to repeat singular elements about the characters, location.. in your description.
282
+
283
+ # Output
284
+
285
+ ```yaml
286
+ title: \""""
287
+
288
+ try:
289
+ print(f"search_video(): calling self.inference_client.text_generation({prompt}, model={TEXT_MODEL}, max_new_tokens=300, temperature=0.65)")
290
+ response = await asyncio.get_event_loop().run_in_executor(
291
+ None,
292
+ lambda: self.inference_client.text_generation(
293
+ prompt,
294
+ model=TEXT_MODEL,
295
+ max_new_tokens=300,
296
+ temperature=0.6
297
+ )
298
+ )
299
+
300
+ #print("response: ", response)
301
+
302
+ response_text = re.sub(r'^\s*\.\s*\n', '', f"title: \"{response.strip()}")
303
+ sanitized_yaml = sanitize_yaml_response(response_text)
304
+
305
+ try:
306
+ result = yaml.safe_load(sanitized_yaml)
307
+ except yaml.YAMLError as e:
308
+ logger.error(f"YAML parsing failed: {str(e)}")
309
+ result = None
310
+
311
+ if not result or not isinstance(result, dict):
312
+ logger.error(f"Invalid result format: {result}")
313
+ return None
314
+
315
+ # Extract fields with defaults
316
+ title = str(result.get('title', '')).strip() or 'Untitled Video'
317
+ description = str(result.get('description', '')).strip() or 'No description available'
318
+ tags = result.get('tags', [])
319
+
320
+ # Ensure tags is a list of strings
321
+ if not isinstance(tags, list):
322
+ tags = []
323
+ tags = [str(t).strip() for t in tags if t and isinstance(t, (str, int, float))]
324
+
325
+ # Generate thumbnail
326
+ #print(f"calling self.generate_thumbnail({title}, {description})")
327
+ try:
328
+ #thumbnail = await self.generate_thumbnail(title, description)
329
+ raise ValueError("thumbnail generation is too buggy and slow right now")
330
+ except Exception as e:
331
+ logger.error(f"Thumbnail generation failed: {str(e)}")
332
+ thumbnail = ""
333
+
334
+ print("got response thumbnail")
335
+ # Return valid result with all required fields
336
+ return {
337
+ 'id': str(uuid.uuid4()),
338
+ 'title': title,
339
+ 'description': description,
340
+ 'thumbnailUrl': thumbnail,
341
+ 'videoUrl': '',
342
+ 'isLatent': True,
343
+ 'useFixedSeed': "webcam" in description.lower(),
344
+ 'seed': generate_seed(),
345
+ 'views': 0,
346
+ 'tags': tags
347
+ }
348
+
349
+ except Exception as e:
350
+ logger.error(f"Search video generation failed: {str(e)}")
351
+ return None
352
+
353
+ async def generate_thumbnail(self, title: str, description: str) -> str:
354
+ """Generate thumbnail using HF image generation"""
355
+ try:
356
+ image_prompt = f"Thumbnail for video titled '{title}': {description}"
357
+
358
+ image = await asyncio.get_event_loop().run_in_executor(
359
+ None,
360
+ lambda: self.inference_client.text_to_image(
361
+ prompt=image_prompt,
362
+ model=IMAGE_MODEL,
363
+ width=1024,
364
+ height=512
365
+ )
366
+ )
367
+
368
+ buffered = io.BytesIO()
369
+ image.save(buffered, format="JPEG")
370
+ img_str = base64.b64encode(buffered.getvalue()).decode()
371
+ return f"data:image/jpeg;base64,{img_str}"
372
+ except Exception as e:
373
+ logger.error(f"Error generating thumbnail: {str(e)}")
374
+ return ""
375
+
376
+ async def generate_caption(self, title: str, description: str) -> str:
377
+ """Generate detailed caption using HF text generation"""
378
+ try:
379
+ prompt = f"""Generate a detailed story for a video named: "{title}"
380
+ Visual description of the video: {description}.
381
+ Instructions: Write the story summary, including the plot, action, what should happen.
382
+ Make it around 200-300 words long.
383
+ A video can be anything from a tutorial, webcam, trailer, movie, live stream etc."""
384
+
385
+ response = await asyncio.get_event_loop().run_in_executor(
386
+ None,
387
+ lambda: self.inference_client.text_generation(
388
+ prompt,
389
+ model=TEXT_MODEL,
390
+ max_new_tokens=180,
391
+ temperature=0.7
392
+ )
393
+ )
394
+
395
+ if "Caption: " in response:
396
+ response = response.replace("Caption: ", "")
397
+
398
+ chunks = f" {response} ".split(". ")
399
+ if len(chunks) > 1:
400
+ text = ". ".join(chunks[:-1])
401
+ else:
402
+ text = response
403
+
404
+ return text.strip()
405
+ except Exception as e:
406
+ logger.error(f"Error generating caption: {str(e)}")
407
+ return ""
408
+
409
+
410
+ def get_config_value(self, role: UserRole, field: str, options: dict = None) -> Any:
411
+ """
412
+ Get the appropriate config value for a user role.
413
+
414
+ Args:
415
+ role: The user role ('anon', 'normal', 'pro', 'admin')
416
+ field: The config field name to retrieve
417
+ options: Optional user-provided options that may override defaults
418
+
419
+ Returns:
420
+ The config value appropriate for the user's role with respect to
421
+ min/max boundaries and user overrides.
422
+ """
423
+ # Select the appropriate config based on user role
424
+ if role == 'admin':
425
+ config = CONFIG_FOR_ADMIN_HF_USERS
426
+ elif role == 'pro':
427
+ config = CONFIG_FOR_PRO_HF_USERS
428
+ elif role == 'normal':
429
+ config = CONFIG_FOR_STANDARD_HF_USERS
430
+ else: # Anonymous users
431
+ config = CONFIG_FOR_ANONYMOUS_USERS
432
+
433
+ # Get the default value for this field from the config
434
+ default_value = config.get(f"default_{field}", None)
435
+
436
+ # For fields that have min/max bounds
437
+ min_field = f"min_{field}"
438
+ max_field = f"max_{field}"
439
+
440
+ # Check if min/max constraints exist for this field
441
+ has_constraints = min_field in config or max_field in config
442
+
443
+ if not has_constraints:
444
+ # For fields without constraints, just return the value from config
445
+ return default_value
446
+
447
+ # Get min and max values from config (if they exist)
448
+ min_value = config.get(min_field, None)
449
+ max_value = config.get(max_field, None)
450
+
451
+ # If user provided options with this field
452
+ if options and field in options:
453
+ user_value = options[field]
454
+
455
+ # Apply constraints if they exist
456
+ if min_value is not None and user_value < min_value:
457
+ return min_value
458
+ if max_value is not None and user_value > max_value:
459
+ return max_value
460
+
461
+ # If within bounds, use the user's value
462
+ return user_value
463
+
464
+ # If no user value, return the default
465
+ return default_value
466
+
467
+ async def _generate_clip_prompt(self, video_id: str, title: str, description: str) -> str:
468
+ """Generate a new prompt for the next clip based on event history"""
469
+ events = self.video_events.get(video_id, [])
470
+ events_json = "\n".join(json.dumps(event) for event in events)
471
+
472
+ prompt = f"""# Context and task
473
+ Please write the caption for a new clip.
474
+
475
+ # Instructions
476
+ 1. Consider the video context and recent events
477
+ 2. Create a natural progression from previous clips
478
+ 3. Take into account user suggestions (chat messages) into the scene
479
+ 4. Don't generate hateful, political, violent or sexual content
480
+ 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)
481
+ 6. Return ONLY the caption text, no additional formatting or explanation
482
+ 7. Write in English, about 200 words.
483
+ 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.
484
+
485
+ # Examples
486
+ Here is a demo scenario, with fake data:
487
+ {{"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)"}}
488
+ {{"time": "2024-11-29T13:36:20Z", "event": "new_chat_message", "username": "MonkeyLover89", "data": "hi"}}
489
+ {{"time": "2024-11-29T13:36:25Z", "event": "new_chat_message", "username": "MonkeyLover89", "data": "more squirrels plz"}}
490
+ {{"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)"}}
491
+
492
+ # Real scenario and data
493
+
494
+ We are inside a video titled "{title}"
495
+ The video is described by: "{description}".
496
+ Here is a summary of the {len(events)} most recent events:
497
+ {events_json}
498
+
499
+ # Your response
500
+ Your caption:"""
501
+
502
+ try:
503
+ response = await asyncio.get_event_loop().run_in_executor(
504
+ None,
505
+ lambda: self.inference_client.text_generation(
506
+ prompt,
507
+ model=TEXT_MODEL,
508
+ max_new_tokens=200,
509
+ temperature=0.7
510
+ )
511
+ )
512
+
513
+ # Clean up the response
514
+ caption = response.strip()
515
+ if caption.lower().startswith("caption:"):
516
+ caption = caption[8:].strip()
517
+
518
+ return caption
519
+
520
+ except Exception as e:
521
+ logger.error(f"Error generating clip prompt: {str(e)}")
522
+ # Fallback to original description if prompt generation fails
523
+ return description
524
+
525
+ async def generate_video(self, title: str, description: str, video_prompt_prefix: str, options: dict, user_role: UserRole = 'anon') -> str:
526
+ """Generate video using available space from pool"""
527
+ video_id = options.get('video_id', str(uuid.uuid4()))
528
+
529
+ # Generate a new prompt based on event history
530
+ #clip_caption = await self._generate_clip_prompt(video_id, title, description)
531
+ clip_caption = f"{video_prompt_prefix} - {title.strip()} - {description.strip()}"
532
+
533
+ # Add the new clip to event history
534
+ self._add_event(video_id, {
535
+ "time": datetime.datetime.utcnow().isoformat() + "Z",
536
+ "event": "new_stream_clip",
537
+ "caption": clip_caption
538
+ })
539
+
540
+ # Use the generated caption as the prompt
541
+ prompt = f"{clip_caption}, {POSITIVE_PROMPT_SUFFIX}"
542
+
543
+ # Get the config values based on user role
544
+ width = self.get_config_value(user_role, 'clip_width', options)
545
+ height = self.get_config_value(user_role, 'clip_height', options)
546
+ num_frames = self.get_config_value(user_role, 'num_frames', options)
547
+ num_inference_steps = self.get_config_value(user_role, 'num_inference_steps', options)
548
+ frame_rate = self.get_config_value(user_role, 'clip_framerate', options)
549
+
550
+ # Log the user role and config values being used
551
+ logger.info(f"Generating video for user with role: {user_role}")
552
+ logger.info(f"Using config values: width={width}, height={height}, num_frames={num_frames}, steps={num_inference_steps}, fps={frame_rate}")
553
+
554
+ json_payload = {
555
+ "inputs": {
556
+ "prompt": prompt,
557
+ },
558
+ "parameters": {
559
+
560
+ # ------------------- settings for LTX-Video -----------------------
561
+
562
+ # this param doesn't exist
563
+ #"enhance_prompt_toggle": options.get('enhance_prompt', False),
564
+
565
+ "negative_prompt": options.get('negative_prompt', NEGATIVE_PROMPT),
566
+
567
+ # note about resolution:
568
+ # we cannot use 720 since it cannot be divided by 32
569
+ "width": width,
570
+ "height": height,
571
+
572
+ # this is a hack to fool LTX-Video into believing our input image is an actual video frame with poor encoding quality
573
+ #"input_image_quality": 70,
574
+
575
+ # LTX-Video requires a frame number divisible by 8, plus one frame
576
+ # note: glitches might appear if you use more than 168 frames
577
+ "num_frames": num_frames,
578
+
579
+ # using 30 steps seems to be enough for most cases, otherwise use 50 for best quality
580
+ # I think using a large number of steps (> 30) might create some overexposure and saturation
581
+ "num_inference_steps": num_inference_steps,
582
+
583
+ # values between 3.0 and 4.0 are nice
584
+ "guidance_scale": options.get('guidance_scale', GUIDANCE_SCALE),
585
+
586
+ "seed": options.get('seed', 42),
587
+
588
+ # ----------------------------------------------------------------
589
+
590
+ # ------------------- settings for Varnish -----------------------
591
+ # This will double the number of frames.
592
+ # You can activate this if you want:
593
+ # - a slow motion effect (in that case use double_num_frames=True and fps=24, 25 or 30)
594
+ # - a HD soap / video game effect (in that case use double_num_frames=True and fps=60)
595
+ "double_num_frames": False, # <- False as we want real-time generation
596
+
597
+ # controls the number of frames per second
598
+ # use this in combination with the num_frames and double_num_frames settings to control the duration and "feel" of your video
599
+ # typical values are: 24, 25, 30, 60
600
+ "fps": frame_rate,
601
+
602
+ # upscale the video using Real-ESRGAN.
603
+ # This upscaling algorithm is relatively fast,
604
+ # but might create an uncanny "3D render" or "drawing" effect.
605
+ "super_resolution": False, # <- False as we want real-time generation
606
+
607
+ # for cosmetic purposes and get a "cinematic" feel, you can optionally add some film grain.
608
+ # it is not recommended to add film grain if your theme doesn't match (film grain is great for black & white, retro looks)
609
+ # and if you do, adding more than 12% will start to negatively impact file size (video codecs aren't great are compressing film grain)
610
+ # 0% = no grain
611
+ # 10% = a bit of grain
612
+ "grain_amount": 0, # value between 0-100
613
+
614
+
615
+ # The range of the CRF scale is 0–51, where:
616
+ # 0 is lossless (for 8 bit only, for 10 bit use -qp 0)
617
+ # 23 is the default
618
+ # 51 is worst quality possible
619
+ # A lower value generally leads to higher quality, and a subjectively sane range is 17–28.
620
+ # Consider 17 or 18 to be visually lossless or nearly so;
621
+ # it should look the same or nearly the same as the input but it isn't technically lossless.
622
+ # 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.
623
+ #"quality": 18,
624
+
625
+ }
626
+ }
627
+
628
+ async with self.endpoint_manager.get_endpoint() as endpoint:
629
+ #logger.info(f"Using endpoint {endpoint.id} for video generation with prompt: {prompt}")
630
+
631
+ async with ClientSession() as session:
632
+ async with session.post(
633
+ endpoint.url,
634
+ headers={
635
+ "Accept": "application/json",
636
+ "Authorization": f"Bearer {HF_TOKEN}",
637
+ "Content-Type": "application/json"
638
+ },
639
+ json=json_payload
640
+ ) as response:
641
+ if response.status != 200:
642
+ error_text = await response.text()
643
+ raise Exception(f"Video generation failed: HTTP {response.status} - {error_text}")
644
+
645
+ result = await response.json()
646
+
647
+ if "error" in result:
648
+ raise Exception(f"Video generation failed: {result['error']}")
649
+
650
+ video_data_uri = result.get("video")
651
+ if not video_data_uri:
652
+ raise Exception("No video data in response")
653
+
654
+ return video_data_uri
655
+
656
+
657
+ async def handle_chat_message(self, data: dict, ws: web.WebSocketResponse) -> dict:
658
+ """Process and broadcast a chat message"""
659
+ video_id = data.get('videoId')
660
+ request_id = data.get('requestId')
661
+
662
+ if not video_id:
663
+ return {
664
+ 'action': 'chat_message',
665
+ 'requestId': request_id,
666
+ 'success': False,
667
+ 'error': 'No video ID provided'
668
+ }
669
+
670
+ # Add chat message to event history
671
+ self._add_event(video_id, {
672
+ "time": datetime.datetime.utcnow().isoformat() + "Z",
673
+ "event": "new_chat_message",
674
+ "username": data.get('username', 'Anonymous'),
675
+ "data": data.get('content', '')
676
+ })
677
+
678
+ room = self.chat_rooms[video_id]
679
+ message_data = {k: v for k, v in data.items() if k != '_ws'}
680
+ room.add_message(message_data)
681
+
682
+ for client in room.connected_clients:
683
+ if client != ws:
684
+ try:
685
+ await client.send_json({
686
+ 'action': 'chat_message',
687
+ 'broadcast': True,
688
+ **message_data
689
+ })
690
+ except Exception as e:
691
+ logger.error(f"Failed to broadcast to client: {e}")
692
+ room.connected_clients.remove(client)
693
+
694
+ return {
695
+ 'action': 'chat_message',
696
+ 'requestId': request_id,
697
+ 'success': True,
698
+ 'message': message_data
699
+ }
700
+
701
+ async def handle_join_chat(self, data: dict, ws: web.WebSocketResponse) -> dict:
702
+ """Handle a request to join a chat room"""
703
+ video_id = data.get('videoId')
704
+ request_id = data.get('requestId')
705
+
706
+ if not video_id:
707
+ return {
708
+ 'action': 'join_chat',
709
+ 'requestId': request_id,
710
+ 'success': False,
711
+ 'error': 'No video ID provided'
712
+ }
713
+
714
+ room = self.chat_rooms[video_id]
715
+ room.connected_clients.add(ws)
716
+ recent_messages = room.get_recent_messages()
717
+
718
+ return {
719
+ 'action': 'join_chat',
720
+ 'requestId': request_id,
721
+ 'success': True,
722
+ 'messages': recent_messages
723
+ }
724
+
725
+ async def handle_leave_chat(self, data: dict, ws: web.WebSocketResponse) -> dict:
726
+ """Handle a request to leave a chat room"""
727
+ video_id = data.get('videoId')
728
+ request_id = data.get('requestId')
729
+
730
+ if not video_id:
731
+ return {
732
+ 'action': 'leave_chat',
733
+ 'requestId': request_id,
734
+ 'success': False,
735
+ 'error': 'No video ID provided'
736
+ }
737
+
738
+ room = self.chat_rooms[video_id]
739
+ if ws in room.connected_clients:
740
+ room.connected_clients.remove(ws)
741
+
742
+ return {
743
+ 'action': 'leave_chat',
744
+ 'requestId': request_id,
745
+ 'success': True
746
+ }
docs/for-bots/flutter-videos/fvp.md ADDED
@@ -0,0 +1,223 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 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")
2
+
3
+ fvp: ^0.31.2 copied to clipboard
4
+
5
+
6
+ ======================================================================================================================================================================
7
+
8
+ 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
9
+
10
+ SDK[Flutter](/packages?q=sdk%3Aflutter "Packages compatible with Flutter SDK")
11
+
12
+ 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")
13
+
14
+ ![liked status: inactive](/static/hash-j60jq2j3/img/like-inactive.svg)![liked status: active](/static/hash-j60jq2j3/img/like-active.svg)123
15
+
16
+
17
+
18
+ ### Metadata
19
+
20
+ video\_player plugin and backend APIs. Support all desktop/mobile platforms with hardware decoders, optimal renders. Supports most formats via FFmpeg
21
+
22
+ More...
23
+
24
+ * Readme
25
+ * [Changelog](/packages/fvp/changelog)
26
+ * [Example](/packages/fvp/example)
27
+ * [Installing](/packages/fvp/install)
28
+ * [Versions](/packages/fvp/versions)
29
+ * [Scores](/packages/fvp/score)
30
+
31
+ FVP [#](#fvp)
32
+ =============
33
+
34
+ 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)
35
+
36
+ Prebuilt example can be download from artifacts of [github actions](https://github.com/wang-bin/fvp/actions).
37
+
38
+ [More examples are here](https://github.com/wang-bin/mdk-examples/tree/master/flutter)
39
+
40
+ project is create with `flutter create -t plugin --platforms=linux,macos,windows,android,ios -i objc -a java fvp`
41
+
42
+ Features [#](#features)
43
+ -----------------------
44
+
45
+ * All platforms: Windows x64(including win7) and arm64, Linux x64 and arm64, macOS, iOS, Android(requires flutter > 3.19 because of minSdk 21).
46
+ * You can choose official implementation or this plugin's
47
+ * Optimal render api: d3d11 for windows, metal for macOS/iOS, OpenGL for Linux and Android(Impeller support)
48
+ * Hardware decoders are enabled by default
49
+ * Dolby Vision support on all platforms
50
+ * Minimal code change for existing [Video Player](https://pub.dev/packages/video_player) apps
51
+ * 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.
52
+ * High performance. Lower cpu, gpu and memory load than libmpv based players.
53
+ * Support audio without video
54
+ * HEVC, VP8 and VP9 transparent video
55
+ * Small footprint. Only about 10MB size increase per cpu architecture(platform dependent).
56
+
57
+ How to Use [#](#how-to-use)
58
+ ---------------------------
59
+
60
+ * 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.
61
+ * **(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.
62
+
63
+ import 'package:fvp/fvp.dart' as fvp;
64
+
65
+ fvp.registerWith(); // in main() or anywhere before creating a player. use fvp for all platforms.
66
+
67
+
68
+ copied to clipboard
69
+
70
+ You can also select the platforms to enable fvp implementation
71
+
72
+ registerWith(options: {'platforms': ['windows', 'macos', 'linux']}); // only these platforms will use this plugin implementation
73
+
74
+
75
+ copied to clipboard
76
+
77
+ To select [other decoders](https://github.com/wang-bin/mdk-sdk/wiki/Decoders), pass options like this
78
+
79
+ fvp.registerWith(options: {
80
+ 'video.decoders': ['D3D11', 'NVDEC', 'FFmpeg']
81
+ //'lowLatency': 1, // optional for network streams
82
+ }); // windows
83
+
84
+
85
+ copied to clipboard
86
+
87
+ [The document](https://pub.dev/documentation/fvp/latest/fvp/registerWith.html) lists all options for `registerWith()`
88
+
89
+ ### Error Handling [#](#error-handling)
90
+
91
+ Errors are usually produced when loading a media.
92
+
93
+ _controller.addListener(() {
94
+ if (_controller.value.hasError && !_controller.value.isCompleted) {
95
+ ...
96
+
97
+
98
+ copied to clipboard
99
+
100
+ ### Backend Player API [#](#backend-player-api)
101
+
102
+ import 'package:fvp/mdk.dart';
103
+
104
+
105
+ copied to clipboard
106
+
107
+ 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).
108
+
109
+ 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)
110
+
111
+ ### VideoPlayerController Extensions [#](#videoplayercontroller-extensions)
112
+
113
+ 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()`.
114
+
115
+ import 'package:fvp/fvp.dart' as fvp;
116
+
117
+ fvp.registerWith(); // in main() or anywhere before creating a player. use fvp for all platforms.
118
+
119
+ // somewhere after controller is initialized
120
+ _controller.record('rtmp://127.0.0.1/live/test');
121
+
122
+
123
+ copied to clipboard
124
+
125
+ Upgrade Dependencies Manually [#](#upgrade-dependencies-manually)
126
+ =================================================================
127
+
128
+ Upgrading binary dependencies can bring new features and backend bug fixes. For macOS and iOS, in your project dir, run
129
+
130
+ pod cache clean mdk
131
+ find . -name Podfile.lock -delete
132
+ rm -rf {mac,i}os/Pods
133
+
134
+
135
+ copied to clipboard
136
+
137
+ 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.
138
+
139
+ Design [#](#design)
140
+ ===================
141
+
142
+ * Playback control api in dart via ffi
143
+ * Manage video renderers in platform specific manners. Receive player ptr via `MethodChannel` to construct player instance and set a renderer target.
144
+ * Callbacks and events in C++ are notified by ReceivePort
145
+ * Function with a one time callback is async and returns a future
146
+
147
+ Enable Subtitles [#](#enable-subtitles)
148
+ =======================================
149
+
150
+ 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.
151
+
152
+ 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
153
+
154
+ fvp.registerWith(options: {
155
+ 'subtitleFontFile': 'https://github.com/mpv-android/mpv-android/raw/master/app/src/main/assets/subfont.ttf'
156
+ });
157
+
158
+
159
+ copied to clipboard
160
+
161
+ DO NOT use flutter-snap [#](#do-not-use-flutter-snap)
162
+ =====================================================
163
+
164
+ 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.
165
+
166
+ Screenshots [#](#screenshots)
167
+ =============================
168
+
169
+ ![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)
170
+
171
+ [
172
+
173
+ 123
174
+
175
+ likes
176
+
177
+ 150
178
+
179
+ points
180
+
181
+ 4.48k
182
+
183
+ downloads
184
+
185
+
186
+
187
+ ](/packages/fvp/score)
188
+
189
+ ### Publisher
190
+
191
+ [![verified publisher](/static/hash-j60jq2j3/img/material-icon-verified.svg "Published by a pub.dev verified publisher")mediadevkit.com](/publishers/mediadevkit.com)
192
+
193
+ ### Weekly Downloads
194
+
195
+ 2024.10.07 - 2025.04.21
196
+
197
+ ### Metadata
198
+
199
+ video\_player plugin and backend APIs. Support all desktop/mobile platforms with hardware decoders, optimal renders. Supports most formats via FFmpeg
200
+
201
+ [Repository (GitHub)](https://github.com/wang-bin/fvp)
202
+
203
+ ### Topics
204
+
205
+ [#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)
206
+
207
+ ### Documentation
208
+
209
+ [API reference](/documentation/fvp/latest/)
210
+
211
+ ### License
212
+
213
+ ![](/static/hash-j60jq2j3/img/material-icon-balance.svg)BSD-3-Clause ([license](/packages/fvp/license))
214
+
215
+ ### Dependencies
216
+
217
+ [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")
218
+
219
+ ### More
220
+
221
+ [Packages that depend on fvp](/packages?q=dependency%3Afvp)
222
+
223
+ {"@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"}
docs/for-bots/flutter-videos/fvp_usage_example__main.dart ADDED
@@ -0,0 +1,369 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Copyright 2013 The Flutter Authors. All rights reserved.
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
4
+
5
+ // This file is used to extract code samples for the README.md file.
6
+ // Run update-excerpts if you modify this file.
7
+
8
+ // ignore_for_file: library_private_types_in_public_api, public_member_api_docs
9
+
10
+ // #docregion basic-example
11
+ import 'dart:io';
12
+
13
+ import 'package:flutter/material.dart';
14
+ import 'package:flutter/services.dart';
15
+ import 'package:video_player/video_player.dart';
16
+ import 'package:fvp/fvp.dart';
17
+ import 'package:logging/logging.dart';
18
+ import 'package:intl/intl.dart';
19
+ import 'package:file_selector/file_selector.dart';
20
+ import 'package:image/image.dart' as img;
21
+
22
+ String? source;
23
+
24
+ void main(List<String> args) async {
25
+ // set logger before registerWith()
26
+ Logger.root.level = Level.ALL;
27
+ final df = DateFormat("HH:mm:ss.SSS");
28
+ Logger.root.onRecord.listen((record) {
29
+ debugPrint(
30
+ '${record.loggerName}.${record.level.name}: ${df.format(record.time)}: ${record.message}',
31
+ wrapWidth: 0x7FFFFFFFFFFFFFFF);
32
+ });
33
+
34
+ final opts = <String, Object>{};
35
+ final globalOpts = <String, Object>{};
36
+ int i = 0;
37
+ bool useFvp = true;
38
+ opts['subtitleFontFile'] =
39
+ 'https://github.com/mpv-android/mpv-android/raw/master/app/src/main/assets/subfont.ttf';
40
+ for (; i < args.length; i++) {
41
+ if (args[i] == '-c:v') {
42
+ opts['video.decoders'] = [args[++i]];
43
+ } else if (args[i] == '-maxSize') {
44
+ // ${w}x${h}
45
+ final size = args[++i].split('x');
46
+ opts['maxWidth'] = int.parse(size[0]);
47
+ opts['maxHeight'] = int.parse(size[1]);
48
+ } else if (args[i] == '-fvp') {
49
+ useFvp = int.parse(args[++i]) > 0;
50
+ } else if (args[i].startsWith('-')) {
51
+ globalOpts[args[i].substring(1)] = args[++i];
52
+ } else {
53
+ break;
54
+ }
55
+ }
56
+ if (globalOpts.isNotEmpty) {
57
+ opts['global'] = globalOpts;
58
+ }
59
+ opts['lowLatency'] = 0;
60
+
61
+ if (i <= args.length - 1) source = args[args.length - 1];
62
+ source ??= await getStartFile();
63
+
64
+ if (useFvp) {
65
+ registerWith(options: opts);
66
+ }
67
+
68
+ runApp(const MaterialApp(
69
+ localizationsDelegates: [DefaultMaterialLocalizations.delegate],
70
+ title: 'Video Demo',
71
+ home: VideoApp()));
72
+ }
73
+
74
+ Future<String?> getStartFile() async {
75
+ if (Platform.isMacOS) {
76
+ WidgetsFlutterBinding.ensureInitialized();
77
+ const hostApi = MethodChannel("openFile");
78
+ return await hostApi.invokeMethod("getCurrentFile");
79
+ }
80
+ return null;
81
+ }
82
+
83
+ Future<String?> showURIPicker(BuildContext context) async {
84
+ final key = GlobalKey<FormState>();
85
+ final src = TextEditingController();
86
+ String? uri;
87
+ await showModalBottomSheet(
88
+ context: context,
89
+ builder: (context) => Container(
90
+ alignment: Alignment.center,
91
+ child: Form(
92
+ key: key,
93
+ child: Padding(
94
+ padding: const EdgeInsets.all(16.0),
95
+ child: Column(
96
+ mainAxisSize: MainAxisSize.max,
97
+ mainAxisAlignment: MainAxisAlignment.start,
98
+ crossAxisAlignment: CrossAxisAlignment.center,
99
+ children: [
100
+ TextFormField(
101
+ controller: src,
102
+ style: const TextStyle(fontSize: 14.0),
103
+ decoration: const InputDecoration(
104
+ border: UnderlineInputBorder(),
105
+ labelText: 'Video URI',
106
+ ),
107
+ validator: (value) {
108
+ if (value == null || value.isEmpty) {
109
+ return 'Please enter a URI';
110
+ }
111
+ return null;
112
+ },
113
+ ),
114
+ Padding(
115
+ padding: const EdgeInsets.symmetric(vertical: 16.0),
116
+ child: ElevatedButton(
117
+ onPressed: () {
118
+ if (key.currentState!.validate()) {
119
+ uri = src.text;
120
+ Navigator.of(context).maybePop();
121
+ }
122
+ },
123
+ child: const Text('Play'),
124
+ ),
125
+ ),
126
+ ],
127
+ ),
128
+ ),
129
+ ),
130
+ ),
131
+ );
132
+ return uri;
133
+ }
134
+
135
+ /// Stateful widget to fetch and then display video content.
136
+ class VideoApp extends StatefulWidget {
137
+ const VideoApp({super.key});
138
+
139
+ @override
140
+ _VideoAppState createState() => _VideoAppState();
141
+ }
142
+
143
+ class _VideoAppState extends State<VideoApp> {
144
+ late VideoPlayerController _controller;
145
+
146
+ @override
147
+ void initState() {
148
+ super.initState();
149
+ if (source != null) {
150
+ if (File(source!).existsSync()) {
151
+ playFile(source!);
152
+ } else {
153
+ playUri(source!);
154
+ }
155
+ } else {
156
+ playUri(
157
+ 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4');
158
+ }
159
+ }
160
+
161
+ void playFile(String path) {
162
+ _controller.dispose();
163
+ _controller = VideoPlayerController.file(File(path));
164
+ _controller.addListener(() {
165
+ setState(() {});
166
+ });
167
+ _controller.initialize().then((_) {
168
+ // Ensure the first frame is shown after the video is initialized, even before the play button has been pressed.
169
+ setState(() {});
170
+ _controller.play();
171
+ });
172
+ }
173
+
174
+ void playUri(String uri) {
175
+ _controller = VideoPlayerController.network(uri);
176
+ _controller.addListener(() {
177
+ setState(() {});
178
+ });
179
+ _controller.initialize().then((_) {
180
+ // Ensure the first frame is shown after the video is initialized, even before the play button has been pressed.
181
+ setState(() {});
182
+ _controller.play();
183
+ });
184
+ }
185
+
186
+ @override
187
+ Widget build(BuildContext context) {
188
+ return Scaffold(
189
+ floatingActionButton: Row(
190
+ children: [
191
+ FloatingActionButton(
192
+ heroTag: 'file',
193
+ tooltip: 'Open [File]',
194
+ onPressed: () async {
195
+ const XTypeGroup typeGroup = XTypeGroup(
196
+ label: 'videos',
197
+ //extensions: <String>['*'],
198
+ );
199
+ final XFile? file =
200
+ await openFile(acceptedTypeGroups: <XTypeGroup>[typeGroup]);
201
+
202
+ if (file != null) {
203
+ playFile(file.path);
204
+ }
205
+ },
206
+ child: const Icon(Icons.file_open),
207
+ ),
208
+ const SizedBox(width: 16.0),
209
+ FloatingActionButton(
210
+ heroTag: 'uri',
211
+ tooltip: 'Open [Uri]',
212
+ onPressed: () {
213
+ showURIPicker(context).then((value) {
214
+ if (value != null) {
215
+ playUri(value);
216
+ }
217
+ });
218
+ },
219
+ child: const Icon(Icons.link),
220
+ ),
221
+ const SizedBox(width: 16.0),
222
+ FloatingActionButton(
223
+ heroTag: 'snapshot',
224
+ tooltip: 'Snapshot',
225
+ onPressed: () {
226
+ if (_controller.value.isInitialized) {
227
+ final info = _controller.getMediaInfo()?.video?[0].codec;
228
+ if (info == null) {
229
+ debugPrint('No video codec info');
230
+ return;
231
+ }
232
+ final width = info.width;
233
+ final height = info.height;
234
+ _controller.snapshot().then((value) {
235
+ if (value != null) {
236
+ // value is rgba data, must encode to png image and save as a file
237
+ final i = img.Image.fromBytes(
238
+ width: width,
239
+ height: height,
240
+ bytes: value.buffer,
241
+ numChannels: 4,
242
+ rowStride: width * 4);
243
+ final savePath =
244
+ '${Directory.systemTemp.path}/snapshot.jpg';
245
+ img.encodeJpgFile(savePath, i, quality: 70).then((value) {
246
+ final msg = value
247
+ ? 'Snapshot saved to $savePath'
248
+ : 'Failed to save snapshot';
249
+ debugPrint(msg);
250
+ // show a toast
251
+ if (context.mounted) {
252
+ ScaffoldMessenger.of(context).showSnackBar(
253
+ SnackBar(
254
+ content: Text(msg),
255
+ duration: const Duration(seconds: 2),
256
+ ),
257
+ );
258
+ }
259
+ });
260
+ }
261
+ });
262
+ }
263
+ },
264
+ child: const Icon(Icons.screenshot),
265
+ ),
266
+ ],
267
+ ),
268
+ body: Center(
269
+ child: _controller.value.isInitialized
270
+ ? AspectRatio(
271
+ aspectRatio: _controller.value.aspectRatio,
272
+ child: Stack(
273
+ alignment: Alignment.bottomCenter,
274
+ children: <Widget>[
275
+ VideoPlayer(_controller),
276
+ _ControlsOverlay(controller: _controller),
277
+ VideoProgressIndicator(_controller, allowScrubbing: true),
278
+ ],
279
+ ),
280
+ )
281
+ : Container(),
282
+ ),
283
+ );
284
+ }
285
+
286
+ @override
287
+ void dispose() {
288
+ super.dispose();
289
+ _controller.dispose();
290
+ }
291
+ }
292
+
293
+ class _ControlsOverlay extends StatelessWidget {
294
+ const _ControlsOverlay({required this.controller});
295
+
296
+ static const List<double> _examplePlaybackRates = <double>[
297
+ 0.25,
298
+ 0.5,
299
+ 1.0,
300
+ 1.5,
301
+ 2.0,
302
+ 3.0,
303
+ 5.0,
304
+ 10.0,
305
+ ];
306
+
307
+ final VideoPlayerController controller;
308
+
309
+ @override
310
+ Widget build(BuildContext context) {
311
+ return Stack(
312
+ children: <Widget>[
313
+ AnimatedSwitcher(
314
+ duration: const Duration(milliseconds: 50),
315
+ reverseDuration: const Duration(milliseconds: 200),
316
+ child: controller.value.isPlaying
317
+ ? const SizedBox.shrink()
318
+ : Container(
319
+ color: Colors.black26,
320
+ child: const Center(
321
+ child: Icon(
322
+ Icons.play_arrow,
323
+ color: Colors.white,
324
+ size: 100.0,
325
+ semanticLabel: 'Play',
326
+ ),
327
+ ),
328
+ ),
329
+ ),
330
+ GestureDetector(
331
+ onTap: () {
332
+ controller.value.isPlaying ? controller.pause() : controller.play();
333
+ },
334
+ ),
335
+ Align(
336
+ alignment: Alignment.topRight,
337
+ child: PopupMenuButton<double>(
338
+ initialValue: controller.value.playbackSpeed,
339
+ tooltip: 'Playback speed',
340
+ onSelected: (double speed) {
341
+ controller.setPlaybackSpeed(speed);
342
+ },
343
+ itemBuilder: (BuildContext context) {
344
+ return <PopupMenuItem<double>>[
345
+ for (final double speed in _examplePlaybackRates)
346
+ PopupMenuItem<double>(
347
+ value: speed,
348
+ child: Text('${speed}x'),
349
+ )
350
+ ];
351
+ },
352
+ child: Padding(
353
+ padding: const EdgeInsets.symmetric(
354
+ // Using less vertical padding as the text is also longer
355
+ // horizontally, so it feels like it would need more spacing
356
+ // horizontally (matching the aspect ratio of the video).
357
+ vertical: 12,
358
+ horizontal: 16,
359
+ ),
360
+ child: Text('${controller.value.playbackSpeed}x'),
361
+ ),
362
+ ),
363
+ ),
364
+ ],
365
+ );
366
+ }
367
+ }
368
+
369
+ // #enddocregion basic-example
docs/for-bots/flutter-videos/fvp_usage_example__multi_textures.dart ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import 'package:flutter/material.dart';
2
+ import 'package:fvp/mdk.dart';
3
+
4
+ void main(List<String> args) async {
5
+ runApp(const SinglePlayerMultipleVideoWidget());
6
+ }
7
+ class SinglePlayerMultipleVideoWidget extends StatefulWidget {
8
+ const SinglePlayerMultipleVideoWidget({Key? key}) : super(key: key);
9
+
10
+ @override
11
+ State<SinglePlayerMultipleVideoWidget> createState() =>
12
+ _SinglePlayerMultipleVideoWidgetState();
13
+ }
14
+
15
+ class _SinglePlayerMultipleVideoWidgetState
16
+ extends State<SinglePlayerMultipleVideoWidget> {
17
+ late final player = Player();
18
+
19
+ @override
20
+ void initState() {
21
+ super.initState();
22
+
23
+ player.media = 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4';
24
+ player.loop = -1;
25
+ player.state = PlaybackState.playing;
26
+ player.updateTexture();
27
+ }
28
+
29
+ @override
30
+ void dispose() {
31
+ player.dispose();
32
+ super.dispose();
33
+ }
34
+
35
+ @override
36
+ Widget build(BuildContext context) {
37
+ return MaterialApp(
38
+ title: 'package:fvp',
39
+ home: Scaffold(
40
+ body: Center(
41
+ child: Row(
42
+ crossAxisAlignment: CrossAxisAlignment.stretch,
43
+ children: [
44
+ Expanded(
45
+ flex: 3,
46
+ child: Column(
47
+ mainAxisSize: MainAxisSize.min,
48
+ children: [
49
+ Expanded(
50
+ child: Card(
51
+ elevation: 8.0,
52
+ color: Colors.black,
53
+ clipBehavior: Clip.antiAlias,
54
+ margin: const EdgeInsets.all(32.0),
55
+ child: Column(
56
+ mainAxisSize: MainAxisSize.max,
57
+ children: [
58
+ Expanded(
59
+ child: Row(
60
+ children: [
61
+ Expanded(
62
+ child: ValueListenableBuilder<int?>(
63
+ valueListenable: player.textureId,
64
+ builder: (context, id, _) => id == null
65
+ ? const SizedBox.shrink()
66
+ : Texture(textureId: id),
67
+ ),
68
+ ),
69
+ Expanded(
70
+ child: ValueListenableBuilder<int?>(
71
+ valueListenable: player.textureId,
72
+ builder: (context, id, _) => id == null
73
+ ? const SizedBox.shrink()
74
+ : Texture(textureId: id),
75
+ ),
76
+ ),
77
+ ],
78
+ ),
79
+ ),
80
+ Expanded(
81
+ child: Row(
82
+ children: [
83
+ Expanded(
84
+ child: ValueListenableBuilder<int?>(
85
+ valueListenable: player.textureId,
86
+ builder: (context, id, _) => id == null
87
+ ? const SizedBox.shrink()
88
+ : Texture(textureId: id),
89
+ ),
90
+ ),
91
+ Expanded(
92
+ child: ValueListenableBuilder<int?>(
93
+ valueListenable: player.textureId,
94
+ builder: (context, id, _) => id == null
95
+ ? const SizedBox.shrink()
96
+ : Texture(textureId: id),
97
+ ),
98
+ ),
99
+ ],
100
+ ),
101
+ ),
102
+ ],
103
+ ),
104
+ ),
105
+ ),
106
+ const SizedBox(height: 32.0),
107
+ ],
108
+ ),
109
+ ),
110
+ ],
111
+ ),
112
+ ),
113
+ ),
114
+ );
115
+ }
116
+ }
docs/for-bots/flutter-videos/video_player.md ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Video Player plugin for Flutter [#](#video-player-plugin-for-flutter)
2
+ =====================================================================
3
+
4
+ [![pub package](https://img.shields.io/pub/v/video_player.svg)](https://pub.dev/packages/video_player)
5
+
6
+ A Flutter plugin for iOS, Android and Web for playing back video on a Widget surface.
7
+
8
+ Android
9
+
10
+ iOS
11
+
12
+ macOS
13
+
14
+ Web
15
+
16
+ **Support**
17
+
18
+ SDK 16+
19
+
20
+ 12.0+
21
+
22
+ 10.14+
23
+
24
+ Any\*
25
+
26
+ ![The example app running in iOS](https://github.com/flutter/packages/blob/main/packages/video_player/video_player/doc/demo_ipod.gif?raw=true)
27
+
28
+ Setup [#](#setup)
29
+ -----------------
30
+
31
+ ### iOS [#](#ios)
32
+
33
+ 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 `<project root>/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.
34
+
35
+ ### Android [#](#android)
36
+
37
+ If you are using network-based videos, ensure that the following permission is present in your Android Manifest file, located in `<project root>/android/app/src/main/AndroidManifest.xml`:
38
+
39
+ <uses-permission android:name="android.permission.INTERNET"/>
40
+
41
+
42
+ copied to clipboard
43
+
44
+ ### macOS [#](#macos)
45
+
46
+ 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)
47
+
48
+ ### Web [#](#web)
49
+
50
+ > 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`.
51
+
52
+ \* 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.
53
+
54
+ 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.
55
+
56
+ Supported Formats [#](#supported-formats)
57
+ -----------------------------------------
58
+
59
+ * 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.
60
+ * 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.
61
+ * 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.
62
+
63
+ Example [#](#example)
64
+ ---------------------
65
+
66
+ import 'package:flutter/material.dart';
67
+ import 'package:video_player/video_player.dart';
68
+
69
+ void main() => runApp(const VideoApp());
70
+
71
+ /// Stateful widget to fetch and then display video content.
72
+ class VideoApp extends StatefulWidget {
73
+ const VideoApp({super.key});
74
+
75
+ @override
76
+ _VideoAppState createState() => _VideoAppState();
77
+ }
78
+
79
+ class _VideoAppState extends State<VideoApp> {
80
+ late VideoPlayerController _controller;
81
+
82
+ @override
83
+ void initState() {
84
+ super.initState();
85
+ _controller = VideoPlayerController.networkUrl(Uri.parse(
86
+ 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4'))
87
+ ..initialize().then((_) {
88
+ // Ensure the first frame is shown after the video is initialized, even before the play button has been pressed.
89
+ setState(() {});
90
+ });
91
+ }
92
+
93
+ @override
94
+ Widget build(BuildContext context) {
95
+ return MaterialApp(
96
+ title: 'Video Demo',
97
+ home: Scaffold(
98
+ body: Center(
99
+ child: _controller.value.isInitialized
100
+ ? AspectRatio(
101
+ aspectRatio: _controller.value.aspectRatio,
102
+ child: VideoPlayer(_controller),
103
+ )
104
+ : Container(),
105
+ ),
106
+ floatingActionButton: FloatingActionButton(
107
+ onPressed: () {
108
+ setState(() {
109
+ _controller.value.isPlaying
110
+ ? _controller.pause()
111
+ : _controller.play();
112
+ });
113
+ },
114
+ child: Icon(
115
+ _controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
116
+ ),
117
+ ),
118
+ ),
119
+ );
120
+ }
121
+
122
+ @override
123
+ void dispose() {
124
+ _controller.dispose();
125
+ super.dispose();
126
+ }
127
+ }
128
+
129
+
130
+ copied to clipboard
131
+
132
+ Usage [#](#usage)
133
+ -----------------
134
+
135
+ 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.
136
+
137
+ This is not complete as of now. You can contribute to this section by [opening a pull request](https://github.com/flutter/packages/pulls).
138
+
139
+ ### Playback speed [#](#playback-speed)
140
+
141
+ 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.
142
+
143
+ To learn about playback speed limitations, see the [`setPlaybackSpeed` method documentation](https://pub.dev/documentation/video_player/latest/video_player/VideoPlayerController/setPlaybackSpeed.html).
144
+
145
+ Furthermore, see the example app for an example playback speed implementation.
docs/for-bots/huggingface/hf-api.md ADDED
The diff for this file is too large to render. See raw diff
 
ios/.gitignore ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ **/dgph
2
+ *.mode1v3
3
+ *.mode2v3
4
+ *.moved-aside
5
+ *.pbxuser
6
+ *.perspectivev3
7
+ **/*sync/
8
+ .sconsign.dblite
9
+ .tags*
10
+ **/.vagrant/
11
+ **/DerivedData/
12
+ Icon?
13
+ **/Pods/
14
+ **/.symlinks/
15
+ profile
16
+ xcuserdata
17
+ **/.generated/
18
+ Flutter/App.framework
19
+ Flutter/Flutter.framework
20
+ Flutter/Flutter.podspec
21
+ Flutter/Generated.xcconfig
22
+ Flutter/ephemeral/
23
+ Flutter/app.flx
24
+ Flutter/app.zip
25
+ Flutter/flutter_assets/
26
+ Flutter/flutter_export_environment.sh
27
+ ServiceDefinitions.json
28
+ Runner/GeneratedPluginRegistrant.*
29
+
30
+ # Exceptions to above rules.
31
+ !default.mode1v3
32
+ !default.mode2v3
33
+ !default.pbxuser
34
+ !default.perspectivev3
ios/Flutter/AppFrameworkInfo.plist ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>CFBundleDevelopmentRegion</key>
6
+ <string>en</string>
7
+ <key>CFBundleExecutable</key>
8
+ <string>App</string>
9
+ <key>CFBundleIdentifier</key>
10
+ <string>io.flutter.flutter.app</string>
11
+ <key>CFBundleInfoDictionaryVersion</key>
12
+ <string>6.0</string>
13
+ <key>CFBundleName</key>
14
+ <string>App</string>
15
+ <key>CFBundlePackageType</key>
16
+ <string>FMWK</string>
17
+ <key>CFBundleShortVersionString</key>
18
+ <string>1.0</string>
19
+ <key>CFBundleSignature</key>
20
+ <string>????</string>
21
+ <key>CFBundleVersion</key>
22
+ <string>1.0</string>
23
+ <key>MinimumOSVersion</key>
24
+ <string>12.0</string>
25
+ </dict>
26
+ </plist>
ios/Flutter/Debug.xcconfig ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2
+ #include "Generated.xcconfig"
ios/Flutter/Release.xcconfig ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2
+ #include "Generated.xcconfig"
ios/Podfile ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Uncomment this line to define a global platform for your project
2
+ # platform :ios, '12.0'
3
+
4
+ # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5
+ ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6
+
7
+ project 'Runner', {
8
+ 'Debug' => :debug,
9
+ 'Profile' => :release,
10
+ 'Release' => :release,
11
+ }
12
+
13
+ def flutter_root
14
+ generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
15
+ unless File.exist?(generated_xcode_build_settings_path)
16
+ raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
17
+ end
18
+
19
+ File.foreach(generated_xcode_build_settings_path) do |line|
20
+ matches = line.match(/FLUTTER_ROOT\=(.*)/)
21
+ return matches[1].strip if matches
22
+ end
23
+ raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
24
+ end
25
+
26
+ require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
27
+
28
+ flutter_ios_podfile_setup
29
+
30
+ target 'Runner' do
31
+ use_frameworks!
32
+ use_modular_headers!
33
+
34
+ flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
35
+ target 'RunnerTests' do
36
+ inherit! :search_paths
37
+ end
38
+ end
39
+
40
+ post_install do |installer|
41
+ installer.pods_project.targets.each do |target|
42
+ flutter_additional_ios_build_settings(target)
43
+ end
44
+ end
ios/Runner.xcodeproj/project.pbxproj ADDED
@@ -0,0 +1,616 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // !$*UTF8*$!
2
+ {
3
+ archiveVersion = 1;
4
+ classes = {
5
+ };
6
+ objectVersion = 54;
7
+ objects = {
8
+
9
+ /* Begin PBXBuildFile section */
10
+ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
11
+ 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
12
+ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
13
+ 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
14
+ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
15
+ 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
16
+ 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
17
+ /* End PBXBuildFile section */
18
+
19
+ /* Begin PBXContainerItemProxy section */
20
+ 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
21
+ isa = PBXContainerItemProxy;
22
+ containerPortal = 97C146E61CF9000F007C117D /* Project object */;
23
+ proxyType = 1;
24
+ remoteGlobalIDString = 97C146ED1CF9000F007C117D;
25
+ remoteInfo = Runner;
26
+ };
27
+ /* End PBXContainerItemProxy section */
28
+
29
+ /* Begin PBXCopyFilesBuildPhase section */
30
+ 9705A1C41CF9048500538489 /* Embed Frameworks */ = {
31
+ isa = PBXCopyFilesBuildPhase;
32
+ buildActionMask = 2147483647;
33
+ dstPath = "";
34
+ dstSubfolderSpec = 10;
35
+ files = (
36
+ );
37
+ name = "Embed Frameworks";
38
+ runOnlyForDeploymentPostprocessing = 0;
39
+ };
40
+ /* End PBXCopyFilesBuildPhase section */
41
+
42
+ /* Begin PBXFileReference section */
43
+ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
44
+ 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
45
+ 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
46
+ 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
47
+ 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
48
+ 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
49
+ 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
50
+ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
51
+ 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
52
+ 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
53
+ 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
54
+ 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
55
+ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
56
+ 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
57
+ 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
58
+ /* End PBXFileReference section */
59
+
60
+ /* Begin PBXFrameworksBuildPhase section */
61
+ 97C146EB1CF9000F007C117D /* Frameworks */ = {
62
+ isa = PBXFrameworksBuildPhase;
63
+ buildActionMask = 2147483647;
64
+ files = (
65
+ );
66
+ runOnlyForDeploymentPostprocessing = 0;
67
+ };
68
+ /* End PBXFrameworksBuildPhase section */
69
+
70
+ /* Begin PBXGroup section */
71
+ 331C8082294A63A400263BE5 /* RunnerTests */ = {
72
+ isa = PBXGroup;
73
+ children = (
74
+ 331C807B294A618700263BE5 /* RunnerTests.swift */,
75
+ );
76
+ path = RunnerTests;
77
+ sourceTree = "<group>";
78
+ };
79
+ 9740EEB11CF90186004384FC /* Flutter */ = {
80
+ isa = PBXGroup;
81
+ children = (
82
+ 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
83
+ 9740EEB21CF90195004384FC /* Debug.xcconfig */,
84
+ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
85
+ 9740EEB31CF90195004384FC /* Generated.xcconfig */,
86
+ );
87
+ name = Flutter;
88
+ sourceTree = "<group>";
89
+ };
90
+ 97C146E51CF9000F007C117D = {
91
+ isa = PBXGroup;
92
+ children = (
93
+ 9740EEB11CF90186004384FC /* Flutter */,
94
+ 97C146F01CF9000F007C117D /* Runner */,
95
+ 97C146EF1CF9000F007C117D /* Products */,
96
+ 331C8082294A63A400263BE5 /* RunnerTests */,
97
+ );
98
+ sourceTree = "<group>";
99
+ };
100
+ 97C146EF1CF9000F007C117D /* Products */ = {
101
+ isa = PBXGroup;
102
+ children = (
103
+ 97C146EE1CF9000F007C117D /* Runner.app */,
104
+ 331C8081294A63A400263BE5 /* RunnerTests.xctest */,
105
+ );
106
+ name = Products;
107
+ sourceTree = "<group>";
108
+ };
109
+ 97C146F01CF9000F007C117D /* Runner */ = {
110
+ isa = PBXGroup;
111
+ children = (
112
+ 97C146FA1CF9000F007C117D /* Main.storyboard */,
113
+ 97C146FD1CF9000F007C117D /* Assets.xcassets */,
114
+ 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
115
+ 97C147021CF9000F007C117D /* Info.plist */,
116
+ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
117
+ 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
118
+ 74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
119
+ 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
120
+ );
121
+ path = Runner;
122
+ sourceTree = "<group>";
123
+ };
124
+ /* End PBXGroup section */
125
+
126
+ /* Begin PBXNativeTarget section */
127
+ 331C8080294A63A400263BE5 /* RunnerTests */ = {
128
+ isa = PBXNativeTarget;
129
+ buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
130
+ buildPhases = (
131
+ 331C807D294A63A400263BE5 /* Sources */,
132
+ 331C807F294A63A400263BE5 /* Resources */,
133
+ );
134
+ buildRules = (
135
+ );
136
+ dependencies = (
137
+ 331C8086294A63A400263BE5 /* PBXTargetDependency */,
138
+ );
139
+ name = RunnerTests;
140
+ productName = RunnerTests;
141
+ productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
142
+ productType = "com.apple.product-type.bundle.unit-test";
143
+ };
144
+ 97C146ED1CF9000F007C117D /* Runner */ = {
145
+ isa = PBXNativeTarget;
146
+ buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
147
+ buildPhases = (
148
+ 9740EEB61CF901F6004384FC /* Run Script */,
149
+ 97C146EA1CF9000F007C117D /* Sources */,
150
+ 97C146EB1CF9000F007C117D /* Frameworks */,
151
+ 97C146EC1CF9000F007C117D /* Resources */,
152
+ 9705A1C41CF9048500538489 /* Embed Frameworks */,
153
+ 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
154
+ );
155
+ buildRules = (
156
+ );
157
+ dependencies = (
158
+ );
159
+ name = Runner;
160
+ productName = Runner;
161
+ productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
162
+ productType = "com.apple.product-type.application";
163
+ };
164
+ /* End PBXNativeTarget section */
165
+
166
+ /* Begin PBXProject section */
167
+ 97C146E61CF9000F007C117D /* Project object */ = {
168
+ isa = PBXProject;
169
+ attributes = {
170
+ BuildIndependentTargetsInParallel = YES;
171
+ LastUpgradeCheck = 1510;
172
+ ORGANIZATIONNAME = "";
173
+ TargetAttributes = {
174
+ 331C8080294A63A400263BE5 = {
175
+ CreatedOnToolsVersion = 14.0;
176
+ TestTargetID = 97C146ED1CF9000F007C117D;
177
+ };
178
+ 97C146ED1CF9000F007C117D = {
179
+ CreatedOnToolsVersion = 7.3.1;
180
+ LastSwiftMigration = 1100;
181
+ };
182
+ };
183
+ };
184
+ buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
185
+ compatibilityVersion = "Xcode 9.3";
186
+ developmentRegion = en;
187
+ hasScannedForEncodings = 0;
188
+ knownRegions = (
189
+ en,
190
+ Base,
191
+ );
192
+ mainGroup = 97C146E51CF9000F007C117D;
193
+ productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
194
+ projectDirPath = "";
195
+ projectRoot = "";
196
+ targets = (
197
+ 97C146ED1CF9000F007C117D /* Runner */,
198
+ 331C8080294A63A400263BE5 /* RunnerTests */,
199
+ );
200
+ };
201
+ /* End PBXProject section */
202
+
203
+ /* Begin PBXResourcesBuildPhase section */
204
+ 331C807F294A63A400263BE5 /* Resources */ = {
205
+ isa = PBXResourcesBuildPhase;
206
+ buildActionMask = 2147483647;
207
+ files = (
208
+ );
209
+ runOnlyForDeploymentPostprocessing = 0;
210
+ };
211
+ 97C146EC1CF9000F007C117D /* Resources */ = {
212
+ isa = PBXResourcesBuildPhase;
213
+ buildActionMask = 2147483647;
214
+ files = (
215
+ 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
216
+ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
217
+ 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
218
+ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
219
+ );
220
+ runOnlyForDeploymentPostprocessing = 0;
221
+ };
222
+ /* End PBXResourcesBuildPhase section */
223
+
224
+ /* Begin PBXShellScriptBuildPhase section */
225
+ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
226
+ isa = PBXShellScriptBuildPhase;
227
+ alwaysOutOfDate = 1;
228
+ buildActionMask = 2147483647;
229
+ files = (
230
+ );
231
+ inputPaths = (
232
+ "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
233
+ );
234
+ name = "Thin Binary";
235
+ outputPaths = (
236
+ );
237
+ runOnlyForDeploymentPostprocessing = 0;
238
+ shellPath = /bin/sh;
239
+ shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
240
+ };
241
+ 9740EEB61CF901F6004384FC /* Run Script */ = {
242
+ isa = PBXShellScriptBuildPhase;
243
+ alwaysOutOfDate = 1;
244
+ buildActionMask = 2147483647;
245
+ files = (
246
+ );
247
+ inputPaths = (
248
+ );
249
+ name = "Run Script";
250
+ outputPaths = (
251
+ );
252
+ runOnlyForDeploymentPostprocessing = 0;
253
+ shellPath = /bin/sh;
254
+ shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
255
+ };
256
+ /* End PBXShellScriptBuildPhase section */
257
+
258
+ /* Begin PBXSourcesBuildPhase section */
259
+ 331C807D294A63A400263BE5 /* Sources */ = {
260
+ isa = PBXSourcesBuildPhase;
261
+ buildActionMask = 2147483647;
262
+ files = (
263
+ 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
264
+ );
265
+ runOnlyForDeploymentPostprocessing = 0;
266
+ };
267
+ 97C146EA1CF9000F007C117D /* Sources */ = {
268
+ isa = PBXSourcesBuildPhase;
269
+ buildActionMask = 2147483647;
270
+ files = (
271
+ 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
272
+ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
273
+ );
274
+ runOnlyForDeploymentPostprocessing = 0;
275
+ };
276
+ /* End PBXSourcesBuildPhase section */
277
+
278
+ /* Begin PBXTargetDependency section */
279
+ 331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
280
+ isa = PBXTargetDependency;
281
+ target = 97C146ED1CF9000F007C117D /* Runner */;
282
+ targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
283
+ };
284
+ /* End PBXTargetDependency section */
285
+
286
+ /* Begin PBXVariantGroup section */
287
+ 97C146FA1CF9000F007C117D /* Main.storyboard */ = {
288
+ isa = PBXVariantGroup;
289
+ children = (
290
+ 97C146FB1CF9000F007C117D /* Base */,
291
+ );
292
+ name = Main.storyboard;
293
+ sourceTree = "<group>";
294
+ };
295
+ 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
296
+ isa = PBXVariantGroup;
297
+ children = (
298
+ 97C147001CF9000F007C117D /* Base */,
299
+ );
300
+ name = LaunchScreen.storyboard;
301
+ sourceTree = "<group>";
302
+ };
303
+ /* End PBXVariantGroup section */
304
+
305
+ /* Begin XCBuildConfiguration section */
306
+ 249021D3217E4FDB00AE95B9 /* Profile */ = {
307
+ isa = XCBuildConfiguration;
308
+ buildSettings = {
309
+ ALWAYS_SEARCH_USER_PATHS = NO;
310
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
311
+ CLANG_ANALYZER_NONNULL = YES;
312
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
313
+ CLANG_CXX_LIBRARY = "libc++";
314
+ CLANG_ENABLE_MODULES = YES;
315
+ CLANG_ENABLE_OBJC_ARC = YES;
316
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
317
+ CLANG_WARN_BOOL_CONVERSION = YES;
318
+ CLANG_WARN_COMMA = YES;
319
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
320
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
321
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
322
+ CLANG_WARN_EMPTY_BODY = YES;
323
+ CLANG_WARN_ENUM_CONVERSION = YES;
324
+ CLANG_WARN_INFINITE_RECURSION = YES;
325
+ CLANG_WARN_INT_CONVERSION = YES;
326
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
327
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
328
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
329
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
330
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
331
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
332
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
333
+ CLANG_WARN_UNREACHABLE_CODE = YES;
334
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
335
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
336
+ COPY_PHASE_STRIP = NO;
337
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
338
+ ENABLE_NS_ASSERTIONS = NO;
339
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
340
+ ENABLE_USER_SCRIPT_SANDBOXING = NO;
341
+ GCC_C_LANGUAGE_STANDARD = gnu99;
342
+ GCC_NO_COMMON_BLOCKS = YES;
343
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
344
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
345
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
346
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
347
+ GCC_WARN_UNUSED_FUNCTION = YES;
348
+ GCC_WARN_UNUSED_VARIABLE = YES;
349
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
350
+ MTL_ENABLE_DEBUG_INFO = NO;
351
+ SDKROOT = iphoneos;
352
+ SUPPORTED_PLATFORMS = iphoneos;
353
+ TARGETED_DEVICE_FAMILY = "1,2";
354
+ VALIDATE_PRODUCT = YES;
355
+ };
356
+ name = Profile;
357
+ };
358
+ 249021D4217E4FDB00AE95B9 /* Profile */ = {
359
+ isa = XCBuildConfiguration;
360
+ DefaultConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
361
+ buildSettings = {
362
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
363
+ CLANG_ENABLE_MODULES = YES;
364
+ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
365
+ ENABLE_BITCODE = NO;
366
+ INFOPLIST_FILE = Runner/Info.plist;
367
+ LD_RUNPATH_SEARCH_PATHS = (
368
+ "$(inherited)",
369
+ "@executable_path/Frameworks",
370
+ );
371
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.aitube2;
372
+ PRODUCT_NAME = "$(TARGET_NAME)";
373
+ SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
374
+ SWIFT_VERSION = 5.0;
375
+ VERSIONING_SYSTEM = "apple-generic";
376
+ };
377
+ name = Profile;
378
+ };
379
+ 331C8088294A63A400263BE5 /* Debug */ = {
380
+ isa = XCBuildConfiguration;
381
+ buildSettings = {
382
+ BUNDLE_LOADER = "$(TEST_HOST)";
383
+ CODE_SIGN_STYLE = Automatic;
384
+ CURRENT_PROJECT_VERSION = 1;
385
+ GENERATE_INFOPLIST_FILE = YES;
386
+ MARKETING_VERSION = 1.0;
387
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.aitube2.RunnerTests;
388
+ PRODUCT_NAME = "$(TARGET_NAME)";
389
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
390
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
391
+ SWIFT_VERSION = 5.0;
392
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
393
+ };
394
+ name = Debug;
395
+ };
396
+ 331C8089294A63A400263BE5 /* Release */ = {
397
+ isa = XCBuildConfiguration;
398
+ buildSettings = {
399
+ BUNDLE_LOADER = "$(TEST_HOST)";
400
+ CODE_SIGN_STYLE = Automatic;
401
+ CURRENT_PROJECT_VERSION = 1;
402
+ GENERATE_INFOPLIST_FILE = YES;
403
+ MARKETING_VERSION = 1.0;
404
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.aitube2.RunnerTests;
405
+ PRODUCT_NAME = "$(TARGET_NAME)";
406
+ SWIFT_VERSION = 5.0;
407
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
408
+ };
409
+ name = Release;
410
+ };
411
+ 331C808A294A63A400263BE5 /* Profile */ = {
412
+ isa = XCBuildConfiguration;
413
+ buildSettings = {
414
+ BUNDLE_LOADER = "$(TEST_HOST)";
415
+ CODE_SIGN_STYLE = Automatic;
416
+ CURRENT_PROJECT_VERSION = 1;
417
+ GENERATE_INFOPLIST_FILE = YES;
418
+ MARKETING_VERSION = 1.0;
419
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.aitube2.RunnerTests;
420
+ PRODUCT_NAME = "$(TARGET_NAME)";
421
+ SWIFT_VERSION = 5.0;
422
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
423
+ };
424
+ name = Profile;
425
+ };
426
+ 97C147031CF9000F007C117D /* Debug */ = {
427
+ isa = XCBuildConfiguration;
428
+ buildSettings = {
429
+ ALWAYS_SEARCH_USER_PATHS = NO;
430
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
431
+ CLANG_ANALYZER_NONNULL = YES;
432
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
433
+ CLANG_CXX_LIBRARY = "libc++";
434
+ CLANG_ENABLE_MODULES = YES;
435
+ CLANG_ENABLE_OBJC_ARC = YES;
436
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
437
+ CLANG_WARN_BOOL_CONVERSION = YES;
438
+ CLANG_WARN_COMMA = YES;
439
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
440
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
441
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
442
+ CLANG_WARN_EMPTY_BODY = YES;
443
+ CLANG_WARN_ENUM_CONVERSION = YES;
444
+ CLANG_WARN_INFINITE_RECURSION = YES;
445
+ CLANG_WARN_INT_CONVERSION = YES;
446
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
447
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
448
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
449
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
450
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
451
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
452
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
453
+ CLANG_WARN_UNREACHABLE_CODE = YES;
454
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
455
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
456
+ COPY_PHASE_STRIP = NO;
457
+ DEBUG_INFORMATION_FORMAT = dwarf;
458
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
459
+ ENABLE_TESTABILITY = YES;
460
+ ENABLE_USER_SCRIPT_SANDBOXING = NO;
461
+ GCC_C_LANGUAGE_STANDARD = gnu99;
462
+ GCC_DYNAMIC_NO_PIC = NO;
463
+ GCC_NO_COMMON_BLOCKS = YES;
464
+ GCC_OPTIMIZATION_LEVEL = 0;
465
+ GCC_PREPROCESSOR_DEFINITIONS = (
466
+ "DEBUG=1",
467
+ "$(inherited)",
468
+ );
469
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
470
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
471
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
472
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
473
+ GCC_WARN_UNUSED_FUNCTION = YES;
474
+ GCC_WARN_UNUSED_VARIABLE = YES;
475
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
476
+ MTL_ENABLE_DEBUG_INFO = YES;
477
+ ONLY_ACTIVE_ARCH = YES;
478
+ SDKROOT = iphoneos;
479
+ TARGETED_DEVICE_FAMILY = "1,2";
480
+ };
481
+ name = Debug;
482
+ };
483
+ 97C147041CF9000F007C117D /* Release */ = {
484
+ isa = XCBuildConfiguration;
485
+ buildSettings = {
486
+ ALWAYS_SEARCH_USER_PATHS = NO;
487
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
488
+ CLANG_ANALYZER_NONNULL = YES;
489
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
490
+ CLANG_CXX_LIBRARY = "libc++";
491
+ CLANG_ENABLE_MODULES = YES;
492
+ CLANG_ENABLE_OBJC_ARC = YES;
493
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
494
+ CLANG_WARN_BOOL_CONVERSION = YES;
495
+ CLANG_WARN_COMMA = YES;
496
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
497
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
498
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
499
+ CLANG_WARN_EMPTY_BODY = YES;
500
+ CLANG_WARN_ENUM_CONVERSION = YES;
501
+ CLANG_WARN_INFINITE_RECURSION = YES;
502
+ CLANG_WARN_INT_CONVERSION = YES;
503
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
504
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
505
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
506
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
507
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
508
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
509
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
510
+ CLANG_WARN_UNREACHABLE_CODE = YES;
511
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
512
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
513
+ COPY_PHASE_STRIP = NO;
514
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
515
+ ENABLE_NS_ASSERTIONS = NO;
516
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
517
+ ENABLE_USER_SCRIPT_SANDBOXING = NO;
518
+ GCC_C_LANGUAGE_STANDARD = gnu99;
519
+ GCC_NO_COMMON_BLOCKS = YES;
520
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
521
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
522
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
523
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
524
+ GCC_WARN_UNUSED_FUNCTION = YES;
525
+ GCC_WARN_UNUSED_VARIABLE = YES;
526
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
527
+ MTL_ENABLE_DEBUG_INFO = NO;
528
+ SDKROOT = iphoneos;
529
+ SUPPORTED_PLATFORMS = iphoneos;
530
+ SWIFT_COMPILATION_MODE = wholemodule;
531
+ SWIFT_OPTIMIZATION_LEVEL = "-O";
532
+ TARGETED_DEVICE_FAMILY = "1,2";
533
+ VALIDATE_PRODUCT = YES;
534
+ };
535
+ name = Release;
536
+ };
537
+ 97C147061CF9000F007C117D /* Debug */ = {
538
+ isa = XCBuildConfiguration;
539
+ DefaultConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
540
+ buildSettings = {
541
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
542
+ CLANG_ENABLE_MODULES = YES;
543
+ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
544
+ ENABLE_BITCODE = NO;
545
+ INFOPLIST_FILE = Runner/Info.plist;
546
+ LD_RUNPATH_SEARCH_PATHS = (
547
+ "$(inherited)",
548
+ "@executable_path/Frameworks",
549
+ );
550
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.aitube2;
551
+ PRODUCT_NAME = "$(TARGET_NAME)";
552
+ SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
553
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
554
+ SWIFT_VERSION = 5.0;
555
+ VERSIONING_SYSTEM = "apple-generic";
556
+ };
557
+ name = Debug;
558
+ };
559
+ 97C147071CF9000F007C117D /* Release */ = {
560
+ isa = XCBuildConfiguration;
561
+ DefaultConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
562
+ buildSettings = {
563
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
564
+ CLANG_ENABLE_MODULES = YES;
565
+ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
566
+ ENABLE_BITCODE = NO;
567
+ INFOPLIST_FILE = Runner/Info.plist;
568
+ LD_RUNPATH_SEARCH_PATHS = (
569
+ "$(inherited)",
570
+ "@executable_path/Frameworks",
571
+ );
572
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.aitube2;
573
+ PRODUCT_NAME = "$(TARGET_NAME)";
574
+ SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
575
+ SWIFT_VERSION = 5.0;
576
+ VERSIONING_SYSTEM = "apple-generic";
577
+ };
578
+ name = Release;
579
+ };
580
+ /* End XCBuildConfiguration section */
581
+
582
+ /* Begin XCConfigurationList section */
583
+ 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
584
+ isa = XCConfigurationList;
585
+ buildConfigurations = (
586
+ 331C8088294A63A400263BE5 /* Debug */,
587
+ 331C8089294A63A400263BE5 /* Release */,
588
+ 331C808A294A63A400263BE5 /* Profile */,
589
+ );
590
+ defaultConfigurationIsVisible = 0;
591
+ defaultConfigurationName = Release;
592
+ };
593
+ 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
594
+ isa = XCConfigurationList;
595
+ buildConfigurations = (
596
+ 97C147031CF9000F007C117D /* Debug */,
597
+ 97C147041CF9000F007C117D /* Release */,
598
+ 249021D3217E4FDB00AE95B9 /* Profile */,
599
+ );
600
+ defaultConfigurationIsVisible = 0;
601
+ defaultConfigurationName = Release;
602
+ };
603
+ 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
604
+ isa = XCConfigurationList;
605
+ buildConfigurations = (
606
+ 97C147061CF9000F007C117D /* Debug */,
607
+ 97C147071CF9000F007C117D /* Release */,
608
+ 249021D4217E4FDB00AE95B9 /* Profile */,
609
+ );
610
+ defaultConfigurationIsVisible = 0;
611
+ defaultConfigurationName = Release;
612
+ };
613
+ /* End XCConfigurationList section */
614
+ };
615
+ rootObject = 97C146E61CF9000F007C117D /* Project object */;
616
+ }
ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <Workspace
3
+ version = "1.0">
4
+ <FileRef
5
+ location = "self:">
6
+ </FileRef>
7
+ </Workspace>
ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>IDEDidComputeMac32BitWarning</key>
6
+ <true/>
7
+ </dict>
8
+ </plist>
ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>PreviewsEnabled</key>
6
+ <false/>
7
+ </dict>
8
+ </plist>
ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <Scheme
3
+ LastUpgradeVersion = "1510"
4
+ version = "1.3">
5
+ <BuildAction
6
+ parallelizeBuildables = "YES"
7
+ buildImplicitDependencies = "YES">
8
+ <BuildActionEntries>
9
+ <BuildActionEntry
10
+ buildForTesting = "YES"
11
+ buildForRunning = "YES"
12
+ buildForProfiling = "YES"
13
+ buildForArchiving = "YES"
14
+ buildForAnalyzing = "YES">
15
+ <BuildableReference
16
+ BuildableIdentifier = "primary"
17
+ BlueprintIdentifier = "97C146ED1CF9000F007C117D"
18
+ BuildableName = "Runner.app"
19
+ BlueprintName = "Runner"
20
+ ReferencedContainer = "container:Runner.xcodeproj">
21
+ </BuildableReference>
22
+ </BuildActionEntry>
23
+ </BuildActionEntries>
24
+ </BuildAction>
25
+ <TestAction
26
+ buildConfiguration = "Debug"
27
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
28
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
29
+ shouldUseLaunchSchemeArgsEnv = "YES">
30
+ <MacroExpansion>
31
+ <BuildableReference
32
+ BuildableIdentifier = "primary"
33
+ BlueprintIdentifier = "97C146ED1CF9000F007C117D"
34
+ BuildableName = "Runner.app"
35
+ BlueprintName = "Runner"
36
+ ReferencedContainer = "container:Runner.xcodeproj">
37
+ </BuildableReference>
38
+ </MacroExpansion>
39
+ <Testables>
40
+ <TestableReference
41
+ skipped = "NO"
42
+ parallelizable = "YES">
43
+ <BuildableReference
44
+ BuildableIdentifier = "primary"
45
+ BlueprintIdentifier = "331C8080294A63A400263BE5"
46
+ BuildableName = "RunnerTests.xctest"
47
+ BlueprintName = "RunnerTests"
48
+ ReferencedContainer = "container:Runner.xcodeproj">
49
+ </BuildableReference>
50
+ </TestableReference>
51
+ </Testables>
52
+ </TestAction>
53
+ <LaunchAction
54
+ buildConfiguration = "Debug"
55
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
56
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
57
+ launchStyle = "0"
58
+ useCustomWorkingDirectory = "NO"
59
+ ignoresPersistentStateOnLaunch = "NO"
60
+ debugDocumentVersioning = "YES"
61
+ debugServiceExtension = "internal"
62
+ allowLocationSimulation = "YES">
63
+ <BuildableProductRunnable
64
+ runnableDebuggingMode = "0">
65
+ <BuildableReference
66
+ BuildableIdentifier = "primary"
67
+ BlueprintIdentifier = "97C146ED1CF9000F007C117D"
68
+ BuildableName = "Runner.app"
69
+ BlueprintName = "Runner"
70
+ ReferencedContainer = "container:Runner.xcodeproj">
71
+ </BuildableReference>
72
+ </BuildableProductRunnable>
73
+ </LaunchAction>
74
+ <ProfileAction
75
+ buildConfiguration = "Profile"
76
+ shouldUseLaunchSchemeArgsEnv = "YES"
77
+ savedToolIdentifier = ""
78
+ useCustomWorkingDirectory = "NO"
79
+ debugDocumentVersioning = "YES">
80
+ <BuildableProductRunnable
81
+ runnableDebuggingMode = "0">
82
+ <BuildableReference
83
+ BuildableIdentifier = "primary"
84
+ BlueprintIdentifier = "97C146ED1CF9000F007C117D"
85
+ BuildableName = "Runner.app"
86
+ BlueprintName = "Runner"
87
+ ReferencedContainer = "container:Runner.xcodeproj">
88
+ </BuildableReference>
89
+ </BuildableProductRunnable>
90
+ </ProfileAction>
91
+ <AnalyzeAction
92
+ buildConfiguration = "Debug">
93
+ </AnalyzeAction>
94
+ <ArchiveAction
95
+ buildConfiguration = "Release"
96
+ revealArchiveInOrganizer = "YES">
97
+ </ArchiveAction>
98
+ </Scheme>
ios/Runner.xcworkspace/contents.xcworkspacedata ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <Workspace
3
+ version = "1.0">
4
+ <FileRef
5
+ location = "group:Runner.xcodeproj">
6
+ </FileRef>
7
+ </Workspace>
ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>IDEDidComputeMac32BitWarning</key>
6
+ <true/>
7
+ </dict>
8
+ </plist>
ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>PreviewsEnabled</key>
6
+ <false/>
7
+ </dict>
8
+ </plist>
ios/Runner/AppDelegate.swift ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Flutter
2
+ import UIKit
3
+
4
+ @main
5
+ @objc class AppDelegate: FlutterAppDelegate {
6
+ override func application(
7
+ _ application: UIApplication,
8
+ didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
9
+ ) -> Bool {
10
+ GeneratedPluginRegistrant.register(with: self)
11
+ return super.application(application, didFinishLaunchingWithOptions: launchOptions)
12
+ }
13
+ }
ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "images" : [
3
+ {
4
+ "size" : "20x20",
5
+ "idiom" : "iphone",
6
+ "filename" : "[email protected]",
7
+ "scale" : "2x"
8
+ },
9
+ {
10
+ "size" : "20x20",
11
+ "idiom" : "iphone",
12
+ "filename" : "[email protected]",
13
+ "scale" : "3x"
14
+ },
15
+ {
16
+ "size" : "29x29",
17
+ "idiom" : "iphone",
18
+ "filename" : "[email protected]",
19
+ "scale" : "1x"
20
+ },
21
+ {
22
+ "size" : "29x29",
23
+ "idiom" : "iphone",
24
+ "filename" : "[email protected]",
25
+ "scale" : "2x"
26
+ },
27
+ {
28
+ "size" : "29x29",
29
+ "idiom" : "iphone",
30
+ "filename" : "[email protected]",
31
+ "scale" : "3x"
32
+ },
33
+ {
34
+ "size" : "40x40",
35
+ "idiom" : "iphone",
36
+ "filename" : "[email protected]",
37
+ "scale" : "2x"
38
+ },
39
+ {
40
+ "size" : "40x40",
41
+ "idiom" : "iphone",
42
+ "filename" : "[email protected]",
43
+ "scale" : "3x"
44
+ },
45
+ {
46
+ "size" : "60x60",
47
+ "idiom" : "iphone",
48
+ "filename" : "[email protected]",
49
+ "scale" : "2x"
50
+ },
51
+ {
52
+ "size" : "60x60",
53
+ "idiom" : "iphone",
54
+ "filename" : "[email protected]",
55
+ "scale" : "3x"
56
+ },
57
+ {
58
+ "size" : "20x20",
59
+ "idiom" : "ipad",
60
+ "filename" : "[email protected]",
61
+ "scale" : "1x"
62
+ },
63
+ {
64
+ "size" : "20x20",
65
+ "idiom" : "ipad",
66
+ "filename" : "[email protected]",
67
+ "scale" : "2x"
68
+ },
69
+ {
70
+ "size" : "29x29",
71
+ "idiom" : "ipad",
72
+ "filename" : "[email protected]",
73
+ "scale" : "1x"
74
+ },
75
+ {
76
+ "size" : "29x29",
77
+ "idiom" : "ipad",
78
+ "filename" : "[email protected]",
79
+ "scale" : "2x"
80
+ },
81
+ {
82
+ "size" : "40x40",
83
+ "idiom" : "ipad",
84
+ "filename" : "[email protected]",
85
+ "scale" : "1x"
86
+ },
87
+ {
88
+ "size" : "40x40",
89
+ "idiom" : "ipad",
90
+ "filename" : "[email protected]",
91
+ "scale" : "2x"
92
+ },
93
+ {
94
+ "size" : "76x76",
95
+ "idiom" : "ipad",
96
+ "filename" : "[email protected]",
97
+ "scale" : "1x"
98
+ },
99
+ {
100
+ "size" : "76x76",
101
+ "idiom" : "ipad",
102
+ "filename" : "[email protected]",
103
+ "scale" : "2x"
104
+ },
105
+ {
106
+ "size" : "83.5x83.5",
107
+ "idiom" : "ipad",
108
+ "filename" : "[email protected]",
109
+ "scale" : "2x"
110
+ },
111
+ {
112
+ "size" : "1024x1024",
113
+ "idiom" : "ios-marketing",
114
+ "filename" : "[email protected]",
115
+ "scale" : "1x"
116
+ }
117
+ ],
118
+ "info" : {
119
+ "version" : 1,
120
+ "author" : "xcode"
121
+ }
122
+ }