-
-
Notifications
You must be signed in to change notification settings - Fork 305
implemented GIF preview feature issue #136 #329
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
See Gifski/PreviewGenerator for an overview
|
ChangesAs usually here is a new demo video: Screen.Recording.2025-04-12.at.10.20.51.PM.mov
I found some more things that needed to be fixed while I was at it:
One last thing: While testing the preview, I noticed a bug where it would flash the wrong frame. I confirmed that this is not a problem with preview, it is a problem with the export itself, if you convert the video it has the same results, the same bug, you can see it here: BounceFlashBug.mov |
|
Uh oh!
< 8000 p class="color-fg-muted my-2 mb-2 ws-normal">There was an error while loading. Please reload this page.
Seems like a bug in the "Bounce" logic. |
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
|
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
Wouldn't it be easier to make a video out of the GIF frames? Then you could reuse everything we already have since it's just a video. |
I actually did try that earlier but I ran into 2 problems:
|
My video branch is here although it's not complete (doesn't have all the fixes from last time) edit: It also doesn't actually run right now, it still has some bugs to be ironed out. |
I'll just try both, starting with the video, and if I can't solve my problems, I'll move on to the custom GIF player. |
I uploaded the commit with the video player, I would say it's in progress, but not finished. Overall, I have mixed feelings about it. On one hand, it does simplify things quite a bit, I was able to simplify most of the code into pure functions and I moved almost everything out of TrimmingAVPlayer, and there is no "PreviewState", "PreviewGenerator", or "LatestItemStream". edit 04/21: I think I have a solution to all these problems. I will upload it in the next couple of days.On the other, AVPlayerView is generally glitchy and hard to work with. I've made some good progress and squashed the major bugs, but I'm sure you will have no problem finding more. For example:
I had hoped I could do something similar for the EditScreen overall. Instead of swapping in and out AVAssets to the TrimmingAVPlayer, I could have one AVMutableComposition and I could swap in and out the tracks as you update the preview. But, I couldn't get this to work reliably, I had assumed you could just track.removeTimeRange(...)
track.insertTimeRange(updatedPreview) but I just end up with blank screen. And everything else I tried all needed an updated AVPlayerItem, which just got me back to where I started. edit on 04/21: Last but not least, when you export a preview of a smaller size, it surrounds the entire rest of the video with a black background. This background, as far as I can tell, is baked into the AVPlayer. Nothing I have tried has gotten rid of it. There are only two solutions that I can think of:
|
I got the actor commit to crash too:
|
So it seems like Metal is the way to go. |
Toggling edit crop regenerates preview even though nothing changed: Screen.Shot.2025-05-30.at.16.46.45.mov |
|
guard assetWriter.status != .failed else { | ||
throw CreateAVAssetError.failedToWrite | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be:
guard assetWriter.status != .failed else { | |
throw CreateAVAssetError.failedToWrite | |
} | |
guard assetWriter.status == .completed else { | |
throw (assetWriter.status.error ?? CreateAVAssetError.failedToWrite) | |
} |
currently it ignores .cancelled
and .unknown
.
} | ||
|
||
guard let overlay else { | ||
underTrimOverlayView = nil | ||
// underTrimOverlayView = nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Leftover?
Gifski/Preview/PreBakedFrames.swift
Outdated
let endRange = Set(numberOfFrames - numberOfFramesToPrecomputeAtTheEnd..<numberOfFrames) | ||
let allFrames = beginningRange.union(endRange) | ||
|
||
let dict = try await allFrames.asyncMap { frameIndex in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no benefit of using asyncMap here. It's not concurrent, it just allows waiting for asynchronous operations. Also, concurrency may be problematic as I'm not sure if CGImageSource is thread-safe.
let dict = try await allFrames.asyncMap { frameIndex in | |
let dict = try await allFrames.map { frameIndex in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could maybe also use some cancellation support:
try Task.checkCancellation()
Gifski/Preview/PreBakedFrames.swift
Outdated
let allFrames = beginningRange.union(endRange) | ||
|
||
let dict = try await allFrames.asyncMap { frameIndex in | ||
guard let image = CGImageSourceCreateImageAtIndex(imageSource, frameIndex, nil) else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use imageSource.createImage
I don't think choppy output is very helpful (and sometimes it seems to even play an frame at wrong size). I would pause the playing, disable play button, and then prioritize generating the current frame the user is on as fast as possible. One frame is usually enough to see the initial quality so you can then choose some different sizes, etc. |
I have a plan to simplify things quite a bit. I'm going to hold off on the suggested changes for now, and spend the next few days radically simplifying everything. Should be much better by then. |
Radical simplificationThe gist is that I removed the second track from the Then, I went through each and every line of code and removed unnecessary parts, moving everything that could, made everything as reusable and generic as possible etc. Each file (other than Utilities) is now about 150 lines or less, and it is meant to be easy to read and understand. Let's go through a summary of the major changes:
The actual conversion of the video to a
Just as before, .task {
for await event in fullPreviewStream.eventStream {
fullPreviewState = event
}
} This new state hides all its internals, but it still can be used to display the proper UI (
It just uses the standard code for adding a video compositor.
Still, here is a rundown of the
Since you said you are unfamiliar with metal, I commented almost every line of code in That's it! |
|
Thanks for working more on this. The new version looks really good! It seems to run great and I was not able to find any bugs or crashes. |
I think Intel Macs support the latest Metal version, just not all the features, so better to set it to latest and just feature detect for unsupported features. |
Changes
|
Sorry about the delay. Been traveling. |
Changes
No worries. UI ChangesThis might still be a bit too early for this, but I was watching some of the WWDC videos last week, and as I'm sure you know, they are moving to the new "liquid glass" look. Do you think there is anything in GIFski preview? Beta 1 is usually a bit too buggy, so I haven't tried it out yet. But from the video, it looks like they are revamping the navigation bar among other changes. |
I removed the complicated typed error handling, I like it much better now 👍 |
I haven't tried it yet, but it's likely some UI tweaks are needed. Although we get a lot for free by using SwiftUI. Let's continue in: #336 |
This looks great now. Thank you so much for all your hard work on this. The end result is really good! 🙏 |
If you're interested in tackling other issues, I'm happy to add bounties to them (not sure if you're doing it for the bounty, learnings, or both). No pressure though, I totally get it if you're feeling burnt out. Cropping and preview ended up being way more challenging than I expected. In case you are interested, I think the priority now is: |
Yeah, let's do it! Obviously, I'm not going to get rich doing this (I probably made less than $1/hr on these 2 issues), but bounties really help motivate me to see the issues through completion (even if they take 3 months longer than anticipated 😅).
I'm not feeling burnt out at all. Let's go!
Definitely interested. Honestly, the hardest part of bug/issue bounty hunting is just finding issues that people are willing to fund and actually respond to. So many PRs end up ignored or forgotten. You’ve been great to work with so far, so keep them coming. If you’ve got issues on other repos you’re funding, I’m happy to check those out too. |
Hi!
I implemented the gif preview features as requested in issue #136
Please watch this short video for a demonstration:
Gifsky_preview_feature.mov
Fixes #136
Here is what the feature does:
Looking at the issue GIF preview feature #136 this should fulfill all of your major requirements.
I will submit this to issueHunt for the ongoing bounty, but any extra tips (you
can use GitHub Sponsor page) would be very welcome!
IssueHunt Summary
Referenced issues
This pull request has been submitted to: