motion_photos 0.0.3 copy "motion_photos: ^0.0.3" to clipboard
motion_photos: ^0.0.3 copied to clipboard

Flutter plugin for detecting motion photos

motion_photos #

The Flutter MotionPhotos Package to detect and extract the video content from the motion photos by ente.

Features #

  • IsMotionPhoto method detects if the give file is MotionPhoto or Not.

  • getMotionVideoIndex method extracts the start and end Index of the MotionPhoto.

  • getMotionVideo method returns [Uint8List] bytes for the video content of the motion photo.

  • getMotionVideo method extracts and returns mp4 file of the video content of the motion photo.

Getting started #

To use this package:

  • Add dependency to your pubspec.yaml file either by directly adding the dependency or by using terminal.
    • Via Terminal
    flutter pub get motion_photos
    
    • Or Add the following in pubspec.yaml file
    dependencies:
        flutter:
            sdk: flutter
        motion_photos:
    

Usage #

MotionPhotos Example App:

  • Clone the codebase.
    git clone git@github.com:ente-io/motion_photos.git
    
  • Go to example folder.
    cd /example
    
  • Run the App.
    flutter run
    
  • Code
    import 'dart:developer';
    import 'dart:io';
    
    import 'package:file_picker/file_picker.dart';
    import 'package:flutter/material.dart';
    import 'package:motion_photos/motion_photos.dart';
    import 'package:video_player/video_player.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: 'Motion Photo Example (from ente.io team)',
        debugShowCheckedModeBanner: false,
        theme: ThemeData(
          primarySwatch: Colors.deepPurple,
        ),
        home: const MyHomePage(title: 'Motion Photo Example'),
      );
    }
    }
    
    class MyHomePage extends StatefulWidget {
    const MyHomePage({super.key, required this.title});
    
    final String title;
    
    @override
    State<MyHomePage> createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
    late String directory;
    List file = List.empty(growable: true);
    late VideoPlayerController _controller;
    late MotionPhotos motionPhotos;
    bool? _isMotionPhoto;
    VideoIndex? videoIndex;
    bool isPicked = false;
    
    Future<void> _pickFromGallery() async {
      FilePickerResult? result;
      try {
        result = await FilePicker.platform.pickFiles(
          type: FileType.image,
          allowMultiple: false,
          allowCompression: false,
        );
        // reset video index
        videoIndex = null;
        _isMotionPhoto = null;
        final path = result!.paths[0]!;
        motionPhotos = MotionPhotos(path);
        _isMotionPhoto = await motionPhotos.isMotionPhoto();
        if (_isMotionPhoto!) {
          videoIndex = await motionPhotos.getMotionVideoIndex();
        }
        setState(() {
          isPicked = true;
        });
      } catch (e) {
        log('Exep: ****$e***');
      }
    }
    
    Future<Widget> _playVideo() async {
      if (isPicked && (_isMotionPhoto ?? false)) {
        try {
          File file = await motionPhotos.getMotionVideoFile();
          _controller = VideoPlayerController.file(file);
          _controller.initialize();
          _controller.setLooping(true);
          _controller.play();
          return VideoPlayer(_controller);
        } catch (e) {
          return Text(e.toString(), style: const TextStyle(color: Colors.red));
        }
      }
      return const SizedBox.shrink();
    }
    
    String printIsMotionPhoto() {
      if (isPicked && _isMotionPhoto != null) {
        return _isMotionPhoto! ? 'Yes' : 'No';
      }
      return 'TBA';
    }
    
    String printVideoIndex() {
      if (isPicked && videoIndex != null) {
        return '''
        Start Index: ${videoIndex!.start}
        End Index: ${videoIndex!.end}
        Video Size: ${videoIndex!.videoLength}
      ''';
      }
      return 'NA';
    }
    
    @override
    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              'Is MotionPhoto: ${printIsMotionPhoto()}',
              style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
            ),
            const SizedBox(height: 20),
            const Text('Video Info',
                style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
            Text(printVideoIndex()),
            const SizedBox(height: 20),
            Container(
              color: Colors.transparent,
              width: double.infinity,
              height: 300,
              child: FutureBuilder<Widget>(
                future: _playVideo(),
                builder: (context, snapshot) {
                  if (snapshot.hasData) {
                    return snapshot.data!;
                  } else {
                    return const Center(child: CircularProgressIndicator());
                  }
                },
              ),
            )
          ],
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            _pickFromGallery();
          },
          child: const Icon(Icons.image),
        ),
      );
    }
    }
      
    

DataTypes Descriptions #

Types Fields
VideoIndex

int start [start index of video in buffer]

int end [end index of video in buffer]

int videoLength [length of the video in buffer]

Method Descriptions #

Methods Parameters Return
isMotionPhoto

String filePath [path of the file]

Future<bool>
getMotionVideoIndex

String filePath [path of the file]

Future<VideoIndex?>
getMotionVideo

String filePath [path of the file]

Future<Uint8List>
getMotionVideoFile

String filePath [path of the file]

String fileName [optional fileName for the destination mp4 file]

Future<File>

Implementation #

A Motion Photo file consists of two parts, a still image and video. Usually, the image is at the start of the file and the video is towards the end. Usually named as IMG_XXXX_XXXX_MP.jpeg

We use two methods to detect and extract motionphoto details:

  • Reads the XMP data of the File to detect whether it is a motion photo and also extracts the video offset to process and retrive the video content of the File in a mp4 format.

  • Traverses the bytes in the File and checks if it contains a mp4 pattern header using boyermoore_search algorithm and also extracts the video offset to process and retrive the video content of the File in a mp4 format.(This is useful in detecting heif file formats).

10
likes
150
points
54
downloads

Publisher

verified publisherente.io

Weekly Downloads

Flutter plugin for detecting motion photos

Homepage

Documentation

API reference

License

MIT (license)

Dependencies

flutter, path_provider, xml

More

Packages that depend on motion_photos