diff --git a/Pixiview/Illust/ViewIllustPage.xaml.cs b/Pixiview/Illust/ViewIllustPage.xaml.cs
index 01a383e..9875fb2 100644
--- a/Pixiview/Illust/ViewIllustPage.xaml.cs
+++ b/Pixiview/Illust/ViewIllustPage.xaml.cs
@@ -523,26 +523,37 @@ namespace Pixiview.Illust
             }
 
             var item = illusts[p];
-
             List<string> extras = new List<string>();
+
             var share = ResourceHelper.Share;
             var preview = Stores.GetPreviewImagePath(item.PreviewUrl);
             if (preview != null)
             {
                 extras.Add(share);
             }
+
             var userDetail = ResourceHelper.UserDetail;
             extras.Add(userDetail);
+
             var related = ResourceHelper.RelatedIllusts;
             extras.Add(related);
-            var saveOriginal = ResourceHelper.SaveOriginal;
 
+#if __IOS__
+            var exportVideo = ResourceHelper.ExportVideo;
+            if (IsAnimateSliderVisible)
+            {
+                extras.Add(exportVideo);
+            }
+#endif
+
+            var saveOriginal = ResourceHelper.SaveOriginal;
             var illustItem = IllustItem;
             var result = await DisplayActionSheet(
                 $"{illustItem.Title} (id: {illustItem.Id})",
                 ResourceHelper.Cancel,
                 saveOriginal,
                 extras.ToArray());
+
             if (result == saveOriginal)
             {
                 SaveOriginalImage(item);
@@ -565,8 +576,57 @@ namespace Pixiview.Illust
                 var page = new RelatedIllustsPage(illustItem);
                 await Navigation.PushAsync(page);
             }
+#if __IOS__
+            else if (result == exportVideo)
+            {
+                ExportVideo();
+            }
+#endif
         }
 
+#if __IOS__
+        private async void ExportVideo()
+        {
+            string msg = ResourceHelper.CantExportVideo;
+
+            if (ugoira != null && ugoiraData != null && ugoiraData.body != null)
+            {
+                if (Stores.CheckUgoiraVideo(ugoiraData.body.originalSrc))
+                {
+                    var flag = await DisplayAlert(ResourceHelper.Operation,
+                        ResourceHelper.AlreadySavedVideo,
+                        ResourceHelper.Yes,
+                        ResourceHelper.No);
+                    if (!flag)
+                    {
+                        return;
+                    }
+                }
+
+                var status = await Permissions.CheckStatusAsync<Permissions.Photos>();
+                if (status != PermissionStatus.Granted)
+                {
+                    status = await Permissions.RequestAsync<Permissions.Photos>();
+                    if (status != PermissionStatus.Granted)
+                    {
+                        App.DebugPrint("access denied to gallery.");
+                        return;
+                    }
+                }
+
+                var success = await Task.Run(ugoira.ExportVideo);
+                if (success != null)
+                {
+                    var result = await FileStore.SaveVideoToGalleryAsync(success);
+
+                    msg = result ?? ResourceHelper.ExportSuccess;
+                }
+            }
+
+            await DisplayAlert(ResourceHelper.Title, msg, ResourceHelper.Ok);
+        }
+#endif
+
         private async void SaveOriginalImage(IllustDetailItem item)
         {
             if (Stores.CheckIllustImage(item.OriginalUrl))
diff --git a/Pixiview/Resources/Languages/zh-CN.xml b/Pixiview/Resources/Languages/zh-CN.xml
index f3b3e2d..5dafb2f 100644
--- a/Pixiview/Resources/Languages/zh-CN.xml
+++ b/Pixiview/Resources/Languages/zh-CN.xml
@@ -44,4 +44,8 @@
     <FavoritesOperation>请选择收藏夹操作</FavoritesOperation>
     <FavoritesReplace>替换</FavoritesReplace>
     <FavoritesCombine>合并</FavoritesCombine>
+    <ExportVideo>导出视频</ExportVideo>
+    <CantExportVideo>无法导出视频,请先下载完成。</CantExportVideo>
+    <AlreadySavedVideo>视频已保存,是否继续?</AlreadySavedVideo>
+    <ExportSuccess>视频已导出到照片库。</ExportSuccess>
 </root>
\ No newline at end of file
diff --git a/Pixiview/Resources/ResourceHelper.cs b/Pixiview/Resources/ResourceHelper.cs
index 7a800f0..2fc9224 100644
--- a/Pixiview/Resources/ResourceHelper.cs
+++ b/Pixiview/Resources/ResourceHelper.cs
@@ -28,6 +28,10 @@ namespace Pixiview.Resources
         public static string FavoritesOperation => GetResource(nameof(FavoritesOperation));
         public static string FavoritesReplace => GetResource(nameof(FavoritesReplace));
         public static string FavoritesCombine => GetResource(nameof(FavoritesCombine));
+        public static string ExportVideo => GetResource(nameof(ExportVideo));
+        public static string CantExportVideo => GetResource(nameof(CantExportVideo));
+        public static string AlreadySavedVideo => GetResource(nameof(AlreadySavedVideo));
+        public static string ExportSuccess => GetResource(nameof(ExportSuccess));
 
         static readonly Dictionary<string, LanguageResource> dict = new Dictionary<string, LanguageResource>();
 
diff --git a/Pixiview/Utils/FileStore.cs b/Pixiview/Utils/FileStore.cs
index a899800..a166831 100644
--- a/Pixiview/Utils/FileStore.cs
+++ b/Pixiview/Utils/FileStore.cs
@@ -1,6 +1,7 @@
 using System.Threading.Tasks;
 using Xamarin.Forms;
 #if __IOS__
+using UIKit;
 using Xamarin.Forms.Platform.iOS;
 #elif __ANDROID__
 using Android.Content;
@@ -13,6 +14,21 @@ namespace Pixiview.Utils
 {
     public class FileStore
     {
+#if __IOS__
+        public static Task<string> SaveVideoToGalleryAsync(string file)
+        {
+            var task = new TaskCompletionSource<string>();
+            if (UIVideo.IsCompatibleWithSavedPhotosAlbum(file))
+            {
+                UIVideo.SaveToPhotosAlbum(file, (path, err) =>
+                {
+                    task.SetResult(err?.ToString());
+                });
+            }
+            return task.Task;
+        }
+#endif
+
         public static Task<string> SaveImageToGalleryAsync(ImageSource image)
         {
 #if __IOS__
diff --git a/Pixiview/Utils/IllustData.cs b/Pixiview/Utils/IllustData.cs
index b579e56..5230626 100644
--- a/Pixiview/Utils/IllustData.cs
+++ b/Pixiview/Utils/IllustData.cs
@@ -266,6 +266,7 @@ namespace Pixiview.Utils
             public string file;
             public int delay;
 
+            public string FilePath;
             public bool Incompleted;
             public int First;
             public int Last;
diff --git a/Pixiview/Utils/Stores.cs b/Pixiview/Utils/Stores.cs
index 7ab234a..a3253e1 100644
--- a/Pixiview/Utils/Stores.cs
+++ b/Pixiview/Utils/Stores.cs
@@ -100,19 +100,19 @@ namespace Pixiview.Utils
             }
         }
 
-        public static ImageSource LoadUgoiraImage(string zip, string frame)
+        public static string LoadUgoiraImage(string zip, string frame)
         {
             var file = Path.Combine(PersonalFolder, ugoiraFolder, zip, frame);
             if (File.Exists(file))
             {
-                try
-                {
-                    return ImageSource.FromFile(file);
-                }
-                catch (Exception ex)
-                {
-                    App.DebugError("load.ugoira", $"failed to load ugoira frame: {zip}/{frame}, error: {ex.Message}");
-                }
+                //try
+                //{
+                return file;
+                //}
+                //catch (Exception ex)
+                //{
+                //    App.DebugError("load.ugoira", $"failed to load ugoira frame: {zip}/{frame}, error: {ex.Message}");
+                //}
             }
             return null;
         }
@@ -138,6 +138,11 @@ namespace Pixiview.Utils
             }
         }
 
+        public static string GetUgoiraVideoPath(string url)
+        {
+            return Path.Combine(PersonalFolder, ugoiraFolder, Path.GetFileNameWithoutExtension(url) + ".mp4");
+        }
+
         private static T ReadObject<T>(string file)
         {
             string content = null;
@@ -397,6 +402,11 @@ namespace Pixiview.Utils
             var file = Path.Combine(PersonalFolder, imageFolder, Path.GetFileName(url));
             return File.Exists(file);
         }
+        public static bool CheckUgoiraVideo(string url)
+        {
+            var file = Path.Combine(PersonalFolder, ugoiraFolder, Path.GetFileNameWithoutExtension(url) + ".mp4");
+            return File.Exists(file);
+        }
 
         public static string GetPreviewImagePath(string url)
         {
diff --git a/Pixiview/Utils/Ugoira.cs b/Pixiview/Utils/Ugoira.cs
index 849e54c..969b913 100644
--- a/Pixiview/Utils/Ugoira.cs
+++ b/Pixiview/Utils/Ugoira.cs
@@ -6,6 +6,15 @@ using System.Threading;
 using System.Threading.Tasks;
 using Pixiview.Illust;
 using Xamarin.Forms;
+using System.Linq;
+#if __IOS__
+using AVFoundation;
+using CoreGraphics;
+using CoreMedia;
+using CoreVideo;
+using Foundation;
+using UIKit;
+#endif
 
 namespace Pixiview.Utils
 {
@@ -148,6 +157,7 @@ namespace Pixiview.Utils
                 var image = Stores.LoadUgoiraImage(zip, frame.file);
                 if (image != null)
                 {
+                    frame.FilePath = image;
                     frames[i] = image;
                 }
                 else
@@ -297,6 +307,7 @@ namespace Pixiview.Utils
                         var file = ExtractImage(zip, data, frame.Offset);
                         if (file != null)
                         {
+                            frame.FilePath = file;
                             frames[i] = ImageSource.FromFile(file);
                         }
                     }
@@ -382,6 +393,104 @@ namespace Pixiview.Utils
                 Done = false;
             }
         }
+
+#if __IOS__
+        public async Task<string> ExportVideo()
+        {
+            if (ugoira == null || ugoira.frames.Any(f => f.Incompleted))
+            {
+                return null;
+            }
+
+            var file = Stores.GetUgoiraVideoPath(ugoira.originalSrc);
+            if (File.Exists(file))
+            {
+                File.Delete(file);
+            }
+            var fileURL = NSUrl.FromFilename(file);
+            var images = ugoira.frames.Select(f => UIImage.FromFile(f.FilePath)).ToArray();
+
+            var videoSettings = new NSMutableDictionary
+            {
+                { AVVideo.CodecKey, AVVideo.CodecH264 },
+                { AVVideo.WidthKey, NSNumber.FromNFloat(images[0].Size.Width) },
+                { AVVideo.HeightKey, NSNumber.FromNFloat(images[0].Size.Height) }
+            };
+            var videoWriter = new AVAssetWriter(fileURL, AVFileType.Mpeg4, out var err);
+            if (err != null)
+            {
+                App.DebugError("export.video", $"failed to create an AVAssetWriter: {err}");
+                return null;
+            }
+            var writerInput = new AVAssetWriterInput(AVMediaType.Video, new AVVideoSettingsCompressed(videoSettings));
+            var sourcePixelBufferAttributes = new NSMutableDictionary
+            {
+                { CVPixelBuffer.PixelFormatTypeKey, NSNumber.FromInt32((int)CVPixelFormatType.CV32ARGB) }
+            };
+            var pixelBufferAdaptor = new AVAssetWriterInputPixelBufferAdaptor(writerInput, sourcePixelBufferAttributes);
+            videoWriter.AddInput(writerInput);
+            if (videoWriter.StartWriting())
+            {
+                videoWriter.StartSessionAtSourceTime(CMTime.Zero);
+                var lastTime = CMTime.Zero;
+                for (int i = 0; i < images.Length; i++)
+                {
+                    while (true)
+                    {
+                        if (writerInput.ReadyForMoreMediaData)
+                        {
+                            // get pixel buffer and fill it with the image
+                            using (CVPixelBuffer pixelBufferImage = pixelBufferAdaptor.PixelBufferPool.CreatePixelBuffer())
+                            {
+                                pixelBufferImage.Lock(CVPixelBufferLock.None);
+
+                                try
+                                {
+                                    IntPtr pxdata = pixelBufferImage.BaseAddress;
+
+                                    if (pxdata != IntPtr.Zero)
+                                    {
+                                        using (var rgbColorSpace = CGColorSpace.CreateDeviceRGB())
+                                        {
+                                            var cgImage = images[i].CGImage;
+                                            var width = cgImage.Width;
+                                            using (CGBitmapContext bitmapContext = new CGBitmapContext(pxdata, width, cgImage.Height,
+                                                8, 4 * (width + width % 8), rgbColorSpace, CGImageAlphaInfo.NoneSkipFirst))
+                                            {
+                                                if (bitmapContext != null)
+                                                {
+                                                    bitmapContext.DrawImage(new CGRect(0, 0, cgImage.Width, cgImage.Height), cgImage);
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                                finally
+                                {
+                                    pixelBufferImage.Unlock(CVPixelBufferLock.None);
+                                }
+
+                                // and finally append buffer to adapter
+                                if (pixelBufferAdaptor.AssetWriterInput.ReadyForMoreMediaData && pixelBufferImage != null)
+                                {
+                                    pixelBufferAdaptor.AppendPixelBufferWithPresentationTime(pixelBufferImage, lastTime);
+                                }
+                            }
+
+                            var frameTime = new CMTime(ugoira.frames[i].delay, 1000);
+                            lastTime = CMTime.Add(lastTime, frameTime);
+                            break;
+                        }
+                        Thread.Sleep(100);
+                    }
+                }
+                writerInput.MarkAsFinished();
+                await videoWriter.FinishWritingAsync();
+                return file;
+            }
+            return null;
+        }
+#endif
     }
 
     public class UgoiraEventArgs : EventArgs