flutter_gpu_video_filters 0.0.20 copy "flutter_gpu_video_filters: ^0.0.20" to clipboard
flutter_gpu_video_filters: ^0.0.20 copied to clipboard

PlatformAndroid

Image filters based on OpenGL fragment shaders with useful video preview widgets

example/lib/main.dart

//ignore_for_file: use_build_context_synchronously
import 'dart:async';
import 'dart:io' show File;
import 'dart:math';

import 'package:path_provider/path_provider.dart';
import 'package:flutter/material.dart' hide Rect;
import 'package:flutter_gpu_video_filters/flutter_gpu_video_filters.dart';
import 'package:flutter_gpu_filters_interface/flutter_gpu_filters_interface.dart';
import 'package:photo_manager/photo_manager.dart';

import 'approved_filters.dart';
import 'filters.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Filters Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.green,
      ),
      home: const ListPage(),
    );
  }
}

class ListPage extends StatelessWidget {
  const ListPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Filters')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: CustomScrollView(
          slivers: [
            SliverFixedExtentList(
              delegate: SliverChildBuilderDelegate((context, index) {
                final item = kFailedFilters[index];
                return Card(
                  child: ListTile(
                    onTap: () {
                      Navigator.push(
                        context,
                        MaterialPageRoute(
                          builder: (context) {
                            return FilterPage(
                              configuration: item.configuration,
                            );
                          },
                        ),
                      );
                    },
                    title: Text(item.name),
                    trailing: Icon(
                      Icons.arrow_forward,
                      color: Theme.of(context).colorScheme.error,
                    ),
                  ),
                );
              }, childCount: kFailedFilters.length),
              itemExtent: 64,
            ),
            SliverFixedExtentList(
              delegate: SliverChildBuilderDelegate((context, index) {
                final item = kFilters[index];
                return Card(
                  child: ListTile(
                    onTap: () {
                      Navigator.push(
                        context,
                        MaterialPageRoute(
                          builder: (context) {
                            return FilterPage(
                              configuration: item.configuration,
                            );
                          },
                        ),
                      );
                    },
                    title: Text(item.name),
                    trailing: Icon(
                      Icons.arrow_forward,
                      color: Theme.of(context).primaryColor,
                    ),
                  ),
                );
              }, childCount: kFilters.length),
              itemExtent: 64,
            ),
          ],
        ),
      ),
    );
  }
}

class FilterPage extends StatefulWidget {
  final GPUFilterConfiguration configuration;

  const FilterPage({super.key, required this.configuration});

  @override
  State<FilterPage> createState() => _FilterPageState();
}

class _FilterPageState extends State<FilterPage> {
  late final VideoPreviewController controller;
  late final GPUVideoPreviewParams previewParams;
  bool previewParamsReady = false;
  static const _videoAsset = 'videos/demo.mp4';
  StreamSubscription<dynamic>? _updateParameter;
  late final random = Random();

  @override
  void initState() {
    super.initState();
    _prepare().whenComplete(() => setState(() {}));
    _updateParameter = Stream.periodic(const Duration(seconds: 1)).listen((
      event,
    ) async {
      if (mounted) {
        final conf = widget.configuration;
        if (conf is GPUBrightnessConfiguration) {
          conf.brightness = random.nextDouble() * 2 - 1;
          await conf.update();
        } else if (conf is GPUMonochromeConfiguration) {
          conf.intensity = random.nextDouble();
          await conf.update();
        }
      }
    });
  }

  @override
  void dispose() {
    controller.dispose();
    widget.configuration.dispose();
    _updateParameter?.cancel();
    super.dispose();
  }

  Future<void> _prepare() async {
    await widget.configuration.prepare();
    previewParams = await GPUVideoPreviewParams.create(widget.configuration);
    previewParamsReady = true;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Preview')),
      floatingActionButton: FloatingActionButton(
        heroTag: null,
        onPressed: () {
          _exportVideo().catchError(
            (e) => ScaffoldMessenger.of(
              context,
            ).showSnackBar(SnackBar(content: Text(e.toString()))),
          );
        },
        tooltip: 'Export video',
        child: const Icon(Icons.save),
      ),
      body: Stack(
        children: [
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: Container(
              child:
                  previewParamsReady
                      ? GPUVideoSurfacePreview(
                        configuration: widget.configuration,
                        onViewCreated: (controller, outputSizeStream) async {
                          this.controller = controller;
                          await controller.setVideoSource(
                            AssetInputSource(_videoAsset),
                          );
                          await widget.configuration.update();
                          await for (final _ in outputSizeStream) {
                            setState(() {});
                          }
                        },
                      )
                      : const Center(child: CircularProgressIndicator()),
            ),
          ),
          Row(
            children: [
              TextButton(
                onPressed: () {
                  controller.pause();
                },
                child: const Text('Pause'),
              ),
              TextButton(
                onPressed: () {
                  controller.play();
                },
                child: const Text('Play'),
              ),
            ],
          ),
        ],
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }

  File? latestFile;

  Future<void> _exportVideo() async {
    const asset = _videoAsset;
    final root = await getTemporaryDirectory();
    final output = File(
      '${root.path}/${DateTime.now().millisecondsSinceEpoch}.${asset.split('.').last}',
    );
    final watch = Stopwatch();
    watch.start();
    final processStream = widget.configuration.exportVideoFile(
      VideoExportConfig(
        latestFile == null
            ? AssetInputSource(asset)
            : FileInputSource(latestFile!),
        output,
      ),
    );
    await for (final progress in processStream) {
      debugPrint('_exportVideo: Exporting file ${(progress * 100).toInt()}%');
    }
    debugPrint(
      '_exportVideo: Exporting file took ${watch.elapsedMilliseconds} milliseconds',
    );
    await _saveFile(output);
    latestFile = output;
    debugPrint('_exportVideo: Exported: ${output.absolute}');
  }
}

class FilterPage2 extends StatefulWidget {
  final GPUFilterConfiguration configuration;

  const FilterPage2({super.key, required this.configuration});

  @override
  State<FilterPage2> createState() => _FilterPageState2();
}

class _FilterPageState2 extends State<FilterPage2> {
  late final VideoPreviewController controller;
  bool previewParamsReady = false;
  static const _videoAsset = 'videos/demo.mp4';

  @override
  void initState() {
    super.initState();
    _prepare().whenComplete(() => setState(() {}));
  }

  @override
  void dispose() {
    controller.dispose();
    widget.configuration.dispose();
    super.dispose();
  }

  Future<void> _prepare() async {
    await widget.configuration.prepare();
    controller = await GPUVideoPreviewController.initialize();
    await controller.connect(widget.configuration);
    await controller.setVideoSource(AssetInputSource(_videoAsset));
    previewParamsReady = true;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Preview')),
      floatingActionButton: FloatingActionButton(
        heroTag: null,
        onPressed: () {
          _exportVideo().catchError(
            (e) => ScaffoldMessenger.of(
              context,
            ).showSnackBar(SnackBar(content: Text(e.toString()))),
          );
        },
        tooltip: 'Export video',
        child: const Icon(Icons.save),
      ),
      body: Stack(
        children: [
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: Container(
              child:
                  previewParamsReady
                      ? VideoPreview(controller: controller)
                      : const Center(child: CircularProgressIndicator()),
            ),
          ),
          Row(
            children: [
              TextButton(
                onPressed: () {
                  controller.pause();
                },
                child: const Text('Pause'),
              ),
              TextButton(
                onPressed: () {
                  controller.play();
                },
                child: const Text('Play'),
              ),
            ],
          ),
        ],
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }

  File? latestFile;

  Future<void> _exportVideo() async {
    const asset = _videoAsset;
    final root = await getTemporaryDirectory();
    final output = File(
      '${root.path}/${DateTime.now().millisecondsSinceEpoch}.${asset.split('.').last}',
    );
    final watch = Stopwatch();
    watch.start();
    final processStream = widget.configuration.exportVideoFile(
      VideoExportConfig(
        latestFile == null
            ? AssetInputSource(asset)
            : FileInputSource(latestFile!),
        output,
      ),
    );
    await for (final progress in processStream) {
      debugPrint('_exportVideo: Exporting file ${(progress * 100).toInt()}%');
    }
    debugPrint(
      '_exportVideo: Exporting file took ${watch.elapsedMilliseconds} milliseconds',
    );
    await _saveFile(output);
    latestFile = output;
    debugPrint('_exportVideo: Exported: ${output.absolute}');
  }
}

Future<void> _saveFile(File input) async {
  try {
    final status = await PhotoManager.requestPermissionExtend(
      requestOption: const PermissionRequestOption(
        androidPermission: AndroidPermission(
          type: RequestType.video,
          mediaLocation: false,
        ),
      ),
    );
    if (status.isAuth || status.hasAccess) {
      await PhotoManager.editor.saveVideo(
        input,
        title: input.uri.pathSegments.last,
      );
    }
  } catch (e, s) {
    debugPrint('Error saving file to gallery: $e');
    debugPrintStack(stackTrace: s);
    rethrow;
  }
}
26
likes
160
points
147
downloads

Publisher

unverified uploader

Weekly Downloads

Image filters based on OpenGL fragment shaders with useful video preview widgets

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

collection, flutter, flutter_gpu_filters_interface, plugin_platform_interface

More

Packages that depend on flutter_gpu_video_filters

Packages that implement flutter_gpu_video_filters