Shotcut — HarmonyOS PC Adaptation

This repository remains the original Qt desktop project (Shotcut, a free, open source, cross-platform video editor built on Qt 6 and the MLT multimedia framework). The HarmonyOS PC wrapper lives under:

harmony_pc/

Open harmony_pc/ in DevEco Studio. The wrapper follows the standard "Qt for HarmonyOS" shape: ArkTS creates a Stage UIAbility, places a full-window XComponent, loads the Qt OpenHarmony QPA plugin, and starts the native libentry.so. The QPA plugin dlopen()s libentry.so and calls its qtmain entry point, which forwards to Shotcut's normal main(). Shotcut's C++ sources are shared with the desktop build; HarmonyOS-only behavior is guarded with Q_OS_OPENHARMONY.

This wrapper uses a project-local Qt 5.15 for HarmonyOS SDK that lives inside this repository — it does not reference any other project's SDK:

harmony_pc/qtforharmony_sdk/
  lib/cmake/Qt5/Qt5Config.cmake
  lib/libQt5Core.so
  lib/libQt5Widgets.so
  lib/libQt5Svg.so
  ...

Application identity is Shotcut's own: the bundle is org.shotcut.shotcut, the label is "Shotcut", and signing is intentionally left empty so DevEco Studio can create/select this project's own signing config.

Layout

  • harmony_pc/build-profile.json5: app-level HarmonyOS build profile (empty signing config — set up your own in DevEco Studio).
  • harmony_pc/AppScope/app.json5: Shotcut bundle metadata (org.shotcut.shotcut).
  • harmony_pc/entry/: HarmonyOS entry module.
  • harmony_pc/entry/src/main/ets: MyAbilityStage, EntryAbility, EntryBackupAbility, and the Index page hosting the XComponent.
  • harmony_pc/entry/src/main/cpp/CMakeLists.txt: native libentry.so build.
  • harmony_pc/entry/src/main/cpp/shotcut_ohos_shell.cpp: the self-contained Qt Widgets UI shell (SHELL build).
  • harmony_pc/qtforharmony_sdk: this project's own bundled Qt 5.15 SDK.
  • harmony_pc/entry/libs/arm64-v8a/: the Qt OpenHarmony QPA plugin plus the TLS (libssl/libcrypto) .sos, copied from the bundled SDK and loaded at runtime.
  • src/main.cpp: gains a Q_OS_OPENHARMONY-guarded extern "C" qtmain alias (default-visibility export for the QPA plugin). The desktop, macOS and Windows paths are unchanged.

Build modes: UI shell vs full app

The native build has two modes, selected by SHOTCUT_FULL_APP (AUTO / ON / OFF, default AUTO in entry/src/main/cpp/CMakeLists.txt):

  • SHELL (AUTO / OFF, the default) — compiles only entry/src/main/cpp/shotcut_ohos_shell.cpp, a self-contained Qt Widgets approximation of Shotcut's editing workspace: the menu bar (File / Edit / View / Timeline / Player / Help), a main toolbar (Open, Save, Export, Append, Split, Ripple Delete, Lift, Zoom), a left Source / Project media bin, a central Player with a painted 16:9 video preview and a transport bar (skip-to-start/end, play/pause that actually advances a playhead, a scrub slider, and HH:MM:SS:FF timecode), a bottom multi-track Timeline (V2 / V1 / A1 / A2) you can really edit — append clips from the bin, click to select, drag the playhead on the ruler, Split at the playhead, Ripple Delete (later clips pull left) and Lift (leave a gap), with a live audio-style waveform on audio tracks — and a right Filters / Properties panel bound to the selected clip (name, track, in/out, duration, and an add-filter list). It has no dependency on Shotcut's src/ sources, MLT, FFmpeg or QML, so it always builds and launches. This proves the ArkTS → XComponent → Qt OpenHarmony QPA → libentry.so → visible, interactive Qt window pipeline against the bundled SDK.
  • FULL (ON) — compiles Shotcut's real application. The CMake FULL branch is now a real build recipe (it mirrors src/CMakeLists.txt: globs Shotcut's C++/QML, excludes the platform-only files, builds CuteLogger, and links MLT++/FFTW + the Qt 6 stack into one libentry.so). It is gated on prerequisites: if a Qt 6 OHOS SDK and the native libraries are not found, it stops with a precise list of what's missing instead of a generic error. Enable with -DSHOTCUT_FULL_APP=ON once those are in place.

FULL-port status — done / blocked

Done (in this repo, no SDK required):

  • src/main.cpp is OHOS-ported: the crash-watchdog child-process fork is skipped under Q_OS_OPENHARMONY (the app is a .so loaded by the QPA plugin and cannot relaunch itself), and the single-instance QLocalServer IPC and QProcess self-restart are guarded out. The extern "C" qtmain entry alias is exported.
  • entry/src/main/cpp/CMakeLists.txt FULL branch is the real build recipe (prerequisite detection + precise missing-deps error + the mirrored build).

Blocked on external binaries (cannot be produced in-repo):

  • A Qt 6 for HarmonyOS SDK with the QML/Quick/Multimedia/Charts stack. None exists locally (only Qt 5.15). Without it the real UI (≈420 .qml) cannot compile. This is the hard blocker.
  • MLT++/MLT, FFmpeg, FFTW, frei0r, SDL2 cross-compiled for OHOS arm64. (A partial FFmpeg arm64 OHOS build was found elsewhere on this machine — …/video_compress_repo/ohos/src/main/cpp/thirdparty/FFmpeg/arm64-v8a/lib/, libavcodec.so.60 + libavformat.so.60 — usable as a starting point for MLT's avformat module, but the full set, FFTW, frei0r and SDL2 still need building.) See harmony_pc/third_party/README.md.

Remaining source-port work (needs the SDK to compile-verify):

  • Finish the Q_OS_OPENHARMONY guards in the ~40 platform-specific files enumerated below (screencapture/X11, native file dialogs, Docker/Flatpak, hardware-codec selection, melt-binary lookup, etc.).
  • Add shotcut_static_plugins.cpp registering the needed Qt/QML/image plugins with Q_IMPORT_PLUGIN (HarmonyOS forbids loading .so from a writable dir).
  • Bundle src/qml + filter-sets + voices into the HAP rawfile and extract to the sandbox on first launch (the EntryAbility.extractShareIfNeeded() hook is already wired); point MLT's MLT_REPOSITORY/MLT_DATA at it.

Full-app port: prerequisites and roadmap

Shotcut targets Qt 6.4+ and the MLT video engine. Before FULL mode can compile, these must be cross-compiled for OHOS arm64 and made discoverable via CMAKE_PREFIX_PATH / CMAKE_FIND_ROOT_PATH:

  1. Qt 6 for HarmonyOS — the bundled SDK here is Qt 5.15 (enough for the SHELL). Shotcut's desktop CMake requires Qt6 Charts, Multimedia, Network, OpenGL, OpenGLWidgets, QuickControls2, QuickWidgets, Sql, WebSockets, Widgets and Xml, plus the Qt 6 OpenHarmony QPA plugin and the QML runtime (Shotcut's UI is ~420 .qml files). A Qt 6 OHOS build is the first prerequisite; bundle it alongside (or replacing) the Qt 5 SDK and point QT_PREFIX at it.
  2. Required native libraries — the multimedia core:
    • MLT++ / MLT (mlt++-7 ≥ 7.36) — the multimedia authoring framework; Shotcut is essentially a Qt front end to MLT. MLT in turn loads its own runtime modules (avformat, qt, frei0r, sdl2, core, ...).
    • FFmpegavformat, avcodec, avutil, swscale, swresample (format/codec support; pulls in x264/x265/vpx/opus/lame/… as configured).
    • FFTW (fftw3) — used by audio spectrum/scopes.
    • frei0r — video effect plugins.
    • SDL2 — cross-platform audio playback.
  3. MLT runtime data & module loading — MLT discovers its modules and presets from an installed share/mlt tree and MLT_REPOSITORY / MLT_DATA / MLT_PROFILES_PATH. HarmonyOS restricts loading arbitrary .so from a writable dir, so either (a) compile the needed MLT modules statically into libentry.so and register the factory entry points directly, or (b) ship them and point the env vars at the extracted sandbox path. Prefer (a) for the modules you keep.
  4. CMake FULL branch — once (1)–(3) exist, replace the FATAL_ERROR stub in entry/src/main/cpp/CMakeLists.txt with a build that mirrors src/CMakeLists.txt, compiling Shotcut's C++/QML into one libentry.so (the same shape as the SHELL target) and linking MLT++/FFmpeg/FFTW/SDL2.
  5. Static Qt & QML plugins — Shotcut loads Qt platform/image/multimedia and QML plugins at runtime. HarmonyOS restricts loading arbitrary .so from a writable dir, so compile the needed ones in and register them with Q_IMPORT_PLUGIN + QPluginLoader::staticInstances(); bundle the QML modules (qmldir + .qml) in the HAP and add their import paths.
  6. Runtime data layout — Shotcut resolves resources (QML, filter presets, LUTs, fonts, MLT profiles, translations, the share/shotcut tree) via QStandardPaths / its app-dir logic. On HarmonyOS these must be shipped inside the HAP (resources/rawfile) and extracted to the app sandbox on first launch — EntryAbility.extractShareIfNeeded() is already wired for this; guard the installed-prefix discovery with Q_OS_OPENHARMONY.
  7. Desktop-only sources — guard out under Q_OS_OPENHARMONY: the QT_MEDIA_BACKEND / QT_QPA_PLATFORM hints in src/main.cpp, the single-instance QLocalServer/QLocalSocket path, the GPU watchdog child-process restart logic, the macOS (macos.mm) and Windows (windowstools, drmingw) backends, and the X11 WindowPicker.

Native dependencies (already present)

The Qt SDK and QPA plugin are bundled in this project:

harmony_pc/qtforharmony_sdk/lib/cmake/Qt5/Qt5Config.cmake
harmony_pc/entry/libs/arm64-v8a/libplugins_platforms_qopenharmony.so

The native build profile passes the relative SDK path by default (harmony_pc/entry/build-profile.json5):

"arguments": "-DQT_PREFIX=qtforharmony_sdk -DSHOTCUT_FULL_APP=AUTO"

If your Qt SDK lives elsewhere, edit harmony_pc/entry/build-profile.json5 and pass an absolute path for QT_PREFIX. The default (relative) value resolves to this project's own harmony_pc/qtforharmony_sdk.

Build

Desktop (unchanged) — the normal Shotcut CMake build.

HarmonyOS PC from the command line:

cd harmony_pc
export DEVECO_SDK_HOME=<command-line-tools>/sdk
<command-line-tools>/bin/ohpm install
<command-line-tools>/bin/hvigorw --mode module -p module=entry@default -p product=default assembleHap --no-daemon

Unsigned output:

harmony_pc/entry/build/default/outputs/default/entry-default-unsigned.hap

DevEco Studio:

  1. Open harmony_pc/ as the project root.
  2. Run ohpm install (or let DevEco Studio sync) so oh-package-lock.json5 and oh_modules/ are generated.
  3. Configure this project's own signing in DevEco Studio before Run on a real HarmonyOS PC/device (File > Project Structure > Signing Configs).
  4. Build the entry HAP.

HarmonyOS behavior

  • The SHELL is a Qt Widgets application. The QPA plugin renders its top-level QMainWindow into the ArkTS XComponent; no QML scene is created. (The FULL app is QML-heavy and additionally needs the Qt 6 QML runtime — see the roadmap above.)
  • The native entry runs the Qt application exactly once per process; the QPA plugin's later onNewWant calls are handled on the ArkTS side.
  • The desktop build path is unchanged.