Today I have released a version of RaidenVideoRipper for Apple devices with macOS and M1/M2/M3/M4 processors (Apple Silicon). RaidenVideoRipper is a quick video editing application that allows you to cut a part of a video file into a new file. You can also make gif, export the audio track to mp3.
Below I will briefly describe what commands I used to do this. The theory of what is happening here, documentation of utilities, can be read at the following links:
https://www.unix.com/man-page/osx/1/otool/
https://www.unix.com/man-page/osx/1/install_name_tool/
https://llvm.org/docs/CommandGuide/llvm-nm.html
https://linux.die.net/man/1/file
https://www.unix.com/man-page/osx/8/SPCTL/
https://linux.die.net/man/1/chmod
https://linux.die.net/man/1/ls
https://man7.org/linux/man-pages/man7/xattr.7.html
https://doc.qt.io/qt-6/macos-deployment.html
First, install Qt on your macOS, also install the environment for Qt Desktop Development. After that, build your project, for example, in Qt Creator, then I will describe what is needed so that dependencies with external dynamic libraries work correctly when distributing the application to end users.
Create a Frameworks directory in the YOUR_APP.app/Contents folder of your application, put external dependencies in it. For example, this is what Frameworks looks like for the RaidenVideoRipper application:
Frameworks
├── DullahanFFmpeg.framework
│ ├── dullahan_ffmpeg.a
│ ├── libavcodec.60.dylib
│ ├── libavdevice.60.dylib
│ ├── libavfilter.9.dylib
│ ├── libavformat.60.dylib
│ ├── libavutil.58.dylib
│ ├── libpostproc.57.dylib
│ ├── libswresample.4.dylib
│ └── libswscale.7.dylib
├── QtCore.framework
│ ├── Headers -> Versions/Current/Headers
│ ├── QtCore -> Versions/Current/QtCore
│ ├── Resources -> Versions/Current/Resources
│ └── Versions
├── QtGui.framework
│ ├── Headers -> Versions/Current/Headers
│ ├── QtGui -> Versions/Current/QtGui
│ ├── Resources -> Versions/Current/Resources
│ └── Versions
├── QtMultimedia.framework
│ ├── Headers -> Versions/Current/Headers
│ ├── QtMultimedia -> Versions/Current/QtMultimedia
│ ├── Resources -> Versions/Current/Resources
│ └── Versions
├── QtMultimediaWidgets.framework
│ ├── Headers -> Versions/Current/Headers
│ ├── QtMultimediaWidgets -> Versions/Current/QtMultimediaWidgets
│ ├── Resources -> Versions/Current/Resources
│ └── Versions
├── QtNetwork.framework
│ ├── Headers -> Versions/Current/Headers
│ ├── QtNetwork -> Versions/Current/QtNetwork
│ ├── Resources -> Versions/Current/Resources
│ └── Versions
└── QtWidgets.framework
├── Headers -> Versions/Current/Headers
├── QtWidgets -> Versions/Current/QtWidgets
├── Resources -> Versions/Current/Resources
└── Versions
For simplicity, I printed out only the second level of nesting.
Next, we print the current dynamic dependencies of your application:
otool -L RaidenVideoRipper
Output for the RaidenVideoRipper binary, which is located in RaidenVideoRipper.app/Contents/MacOS:
RaidenVideoRipper:
@rpath/DullahanFFmpeg.framework/dullahan_ffmpeg.a (compatibility version 0.0.0, current version 0.0.0)
@rpath/QtMultimediaWidgets.framework/Versions/A/QtMultimediaWidgets (compatibility version 6.0.0, current version 6.8.1)
@rpath/QtWidgets.framework/Versions/A/QtWidgets (compatibility version 6.0.0, current version 6.8.1)
@rpath/QtMultimedia.framework/Versions/A/QtMultimedia (compatibility version 6.0.0, current version 6.8.1)
@rpath/QtGui.framework/Versions/A/QtGui (compatibility version 6.0.0, current version 6.8.1)
/System/Library/Frameworks/AppKit.framework/Versions/C/AppKit (compatibility version 45.0.0, current version 2575.20.19)
/System/Library/Frameworks/ImageIO.framework/Versions/A/ImageIO (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/Metal.framework/Versions/A/Metal (compatibility version 1.0.0, current version 367.4.0)
@rpath/QtNetwork.framework/Versions/A/QtNetwork (compatibility version 6.0.0, current version 6.8.1)
@rpath/QtCore.framework/Versions/A/QtCore (compatibility version 6.0.0, current version 6.8.1)
/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit (compatibility version 1.0.0, current version 275.0.0)
/System/Library/Frameworks/DiskArbitration.framework/Versions/A/DiskArbitration (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/UniformTypeIdentifiers.framework/Versions/A/UniformTypeIdentifiers (compatibility version 1.0.0, current version 709.0.0)
/System/Library/Frameworks/AGL.framework/Versions/A/AGL (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/OpenGL.framework/Versions/A/OpenGL (compatibility version 1.0.0, current version 1.0.0)
/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 1800.101.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1351.0.0)
As you can see in RaidenVideoRipper in dependencies Qt and dullahan_ffmpeg. Dullahan FFmpeg is a fork of FFmpeg that encapsulates its functionality in a dynamic library, with the ability to get the current progress of execution and cancel, using C procedures.
Next, replace the paths of the application and all necessary libraries using install_name_tool.
The command for this is:
install_name_tool -change old_path new_path target
Example of use:
install_name_tool -change /usr/local/lib/libavfilter.9.dylib @rpath/DullahanFFmpeg.framework/libavfilter.9.dylib dullahan_ffmpeg.a
After you have entered all the correct paths, the application should start correctly. Check that all paths to the libraries are relative, move the binary, and open it again.
If you see any error, check the paths via otool and change them again via install_name_tool.
There is also an error with dependency confusion, when the library you replaced does not have a symbol in the table, you can check the presence or absence of the symbol like this:
Once executed, you will see the entire symbol table of the library or application.
It is also possible that you will copy dependencies of the wrong architecture, you can check this using file:
The file utility will show you what architecture a library or application belongs to.
Also, Qt requires the presence of the Plugins folder in the Contents folder of your YOUR_APP.app directory, copy the plugins from Qt to Contents. Next, check the functionality of the application, after that you can start optimizing the Plugins folder by deleting items from this folder and testing the application.
macOS Security
Once you have copied all the dependencies and fixed the paths for dynamic linking, you will need to sign the application with the developer’s signature, and also send a version of the application to Apple for notarization.
If you don’t have $100 for a developer license or don’t want to sign anything, then write instructions for your users on how to launch the application.
This instruction also works for RaidenVideoRipper:
- Disabling Gatekeeper: spctl –master-disable
- Allow launch from any sources in Privacy & Security: Allow applications switch to Anywhere
- Remove quarantine flag after downloading from zip or dmg application: xattr -d com.apple.quarantine app.dmg
- Check that the quarantine flag (com.apple.quarantine) is missing: ls -l@ app.dmg
- Add confirm the launch of the application if necessary in Privacy & Security
The error with the quarantine flag is usually reproduced by the error “The application is damaged” appearing on the user’s screen. In this case, you need to remove the quarantine flag from the metadata.
Link to RaidenVideoRipper build for Apple Silicon:
https://github.com/demensdeum/RaidenVideoRipper/releases/download/1.0.1.0/RaidenVideoRipper-1.0.1.0.dmg