libytdlp-sys 0.6.0

Raw FFI bindings to the libytdlp C ABI
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
cmake_minimum_required(VERSION 3.22)

file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/VERSION" LIBYTDLP_VERSION_RAW LIMIT_COUNT 1)
string(STRIP "${LIBYTDLP_VERSION_RAW}" LIBYTDLP_VERSION_RAW)
if(LIBYTDLP_VERSION_RAW STREQUAL "")
    message(FATAL_ERROR "VERSION file is empty or missing: ${CMAKE_CURRENT_SOURCE_DIR}/VERSION")
endif()

project(libytdlp VERSION ${LIBYTDLP_VERSION_RAW} LANGUAGES C CXX)

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)

include(GNUInstallDirs)
include(CTest)

option(LIBYTDLP_SANITIZE_ADDRESS "Build with AddressSanitizer" OFF)
option(LIBYTDLP_SANITIZE_UNDEFINED "Build with UndefinedBehaviorSanitizer" OFF)
set(LIBYTDLP_DEPLOYMENT_PROFILE "FULL_BUNDLED" CACHE STRING "Dependency provisioning profile")
set_property(CACHE LIBYTDLP_DEPLOYMENT_PROFILE PROPERTY STRINGS
    FULL_BUNDLED BUNDLED_YTDLP_SYSTEM_PYTHON SYSTEM_YTDLP_SYSTEM_PYTHON)

if(NOT LIBYTDLP_DEPLOYMENT_PROFILE MATCHES
    "^(FULL_BUNDLED|BUNDLED_YTDLP_SYSTEM_PYTHON|SYSTEM_YTDLP_SYSTEM_PYTHON)$")
    message(FATAL_ERROR "LIBYTDLP_DEPLOYMENT_PROFILE must be FULL_BUNDLED, "
        "BUNDLED_YTDLP_SYSTEM_PYTHON or SYSTEM_YTDLP_SYSTEM_PYTHON")
endif()

set(LIBYTDLP_BUNDLE_YTDLP ON)

if(LIBYTDLP_DEPLOYMENT_PROFILE STREQUAL "SYSTEM_YTDLP_SYSTEM_PYTHON")
    set(LIBYTDLP_BUNDLE_YTDLP OFF)
endif()

set(LIBYTDLP_SANITIZERS "")
if(LIBYTDLP_SANITIZE_ADDRESS)
    list(APPEND LIBYTDLP_SANITIZERS address)
endif()
if(LIBYTDLP_SANITIZE_UNDEFINED)
    list(APPEND LIBYTDLP_SANITIZERS undefined)
endif()

find_package(Python COMPONENTS Interpreter Development.Embed REQUIRED)

execute_process(
    COMMAND ${Python_EXECUTABLE} -c "import yt_dlp; print(yt_dlp.version.__version__)"
    OUTPUT_VARIABLE LIBYTDLP_YTDLP_VERSION
    OUTPUT_STRIP_TRAILING_WHITESPACE
    ERROR_QUIET
)
if(LIBYTDLP_YTDLP_VERSION)
    message(STATUS "yt-dlp on system Python: ${LIBYTDLP_YTDLP_VERSION}")
else()
    message(STATUS "yt-dlp not importable on system Python; tests fall back to third_party/yt-dlp")
endif()

if(LIBYTDLP_DEPLOYMENT_PROFILE STREQUAL "SYSTEM_YTDLP_SYSTEM_PYTHON"
    AND NOT LIBYTDLP_YTDLP_VERSION)
    message(FATAL_ERROR "SYSTEM_YTDLP_SYSTEM_PYTHON needs yt-dlp importable on "
        "${Python_EXECUTABLE} (pip install \"yt-dlp==<pinned>\", see pyproject.toml)")
endif()

execute_process(
    COMMAND git -C ${CMAKE_CURRENT_SOURCE_DIR}/third_party/yt-dlp rev-parse HEAD
    OUTPUT_VARIABLE LIBYTDLP_YTDLP_COMMIT
    OUTPUT_STRIP_TRAILING_WHITESPACE
    ERROR_QUIET
)
if(NOT LIBYTDLP_YTDLP_COMMIT)
    set(LIBYTDLP_YTDLP_COMMIT "unknown")
endif()
message(STATUS "Deployment profile: ${LIBYTDLP_DEPLOYMENT_PROFILE}")
message(STATUS "yt-dlp commit: ${LIBYTDLP_YTDLP_COMMIT}")

configure_file(
    "${CMAKE_CURRENT_SOURCE_DIR}/include/libytdlp/version.h.in"
    "${CMAKE_CURRENT_BINARY_DIR}/generated/include/libytdlp/version.h"
    @ONLY
)

add_library(ytdlp SHARED
    native/api.cpp
    native/bridge.cpp
    native/bridge_embedded.cpp
    native/install_dirs.cpp
    native/jobs.cpp
    native/runtime.cpp
)
add_library(libytdlp::ytdlp ALIAS ytdlp)

add_library(ytdlp_static STATIC
    native/api.cpp
    native/bridge.cpp
    native/bridge_embedded.cpp
    native/install_dirs.cpp
    native/jobs.cpp
    native/runtime.cpp
)
add_library(libytdlp::ytdlp_static ALIAS ytdlp_static)

set(LIBYTDLP_BRIDGE_SOURCES
    ${CMAKE_CURRENT_SOURCE_DIR}/python/libytdlp_bridge/options.py
    ${CMAKE_CURRENT_SOURCE_DIR}/python/libytdlp_bridge/serialize.py
    ${CMAKE_CURRENT_SOURCE_DIR}/python/libytdlp_bridge/extract.py
    ${CMAKE_CURRENT_SOURCE_DIR}/python/libytdlp_bridge/download.py
    ${CMAKE_CURRENT_SOURCE_DIR}/python/libytdlp_bridge/__init__.py
)

add_custom_command(
    OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/generated/bridge_sources.h
    COMMAND ${Python_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/embed_bridge.py
        ${CMAKE_CURRENT_BINARY_DIR}/generated/bridge_sources.h
        ${LIBYTDLP_BRIDGE_SOURCES}
    DEPENDS ${LIBYTDLP_BRIDGE_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/tools/embed_bridge.py
    COMMENT "Embedding the Python bridge for file-less runtimes"
    VERBATIM
)

add_custom_target(libytdlp_bridge_sources
    DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/generated/bridge_sources.h
)

foreach(target ytdlp ytdlp_static)
    add_dependencies(${target} libytdlp_bridge_sources)

    target_include_directories(${target}
        PUBLIC
            $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
            $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/generated/include>
            $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
        PRIVATE
            ${CMAKE_CURRENT_SOURCE_DIR}/native
            ${CMAKE_CURRENT_BINARY_DIR}/generated
    )
    target_compile_definitions(${target} PRIVATE
        LIBYTDLP_BRIDGE_DIR="${CMAKE_CURRENT_SOURCE_DIR}/python"
        LIBYTDLP_YTDLP_DIR="${CMAKE_CURRENT_SOURCE_DIR}/third_party/yt-dlp"
        LIBYTDLP_DEPLOYMENT_PROFILE="${LIBYTDLP_DEPLOYMENT_PROFILE}"
        LIBYTDLP_YTDLP_COMMIT="${LIBYTDLP_YTDLP_COMMIT}"
    )
endforeach()
target_link_libraries(ytdlp PRIVATE Python::Python)
target_link_libraries(ytdlp_static PUBLIC Python::Python)
target_compile_definitions(ytdlp PRIVATE LY_BUILDING_SHARED)
target_compile_definitions(ytdlp_static PUBLIC LY_STATIC)
if(WIN32)
    set_target_properties(ytdlp_static PROPERTIES
        OUTPUT_NAME ytdlp_static
        POSITION_INDEPENDENT_CODE ON
        CXX_VISIBILITY_PRESET hidden
        VISIBILITY_INLINES_HIDDEN ON
    )
else()
    set_target_properties(ytdlp_static PROPERTIES
        OUTPUT_NAME ytdlp
        POSITION_INDEPENDENT_CODE ON
        CXX_VISIBILITY_PRESET hidden
        VISIBILITY_INLINES_HIDDEN ON
    )
endif()
set_target_properties(ytdlp PROPERTIES
    OUTPUT_NAME ytdlp
    VERSION ${PROJECT_VERSION}
    SOVERSION 0
    CXX_VISIBILITY_PRESET hidden
    VISIBILITY_INLINES_HIDDEN ON
)

if(LIBYTDLP_SANITIZERS)
    list(JOIN LIBYTDLP_SANITIZERS "," LIBYTDLP_SANITIZER_FLAGS)
    target_compile_options(ytdlp PRIVATE -fsanitize=${LIBYTDLP_SANITIZER_FLAGS} -fno-omit-frame-pointer)
    target_link_options(ytdlp PRIVATE -fsanitize=${LIBYTDLP_SANITIZER_FLAGS})
    target_compile_options(ytdlp_static PRIVATE -fsanitize=${LIBYTDLP_SANITIZER_FLAGS} -fno-omit-frame-pointer)
    target_link_options(ytdlp_static PUBLIC -fsanitize=${LIBYTDLP_SANITIZER_FLAGS})
endif()

if(NOT WIN32)
    set(THREADS_PREFER_PTHREAD_FLAG ON)
    find_package(Threads REQUIRED)
    target_link_libraries(ytdlp PRIVATE Threads::Threads)
    target_link_libraries(ytdlp_static PUBLIC Threads::Threads)

    if(APPLE)
        target_link_libraries(ytdlp_static PUBLIC c++)
    else()
        target_link_libraries(ytdlp_static PUBLIC stdc++)
    endif()
endif()

option(LIBYTDLP_BUILD_RUST "Build Rust bindings via Corrosion" OFF)
if(LIBYTDLP_BUILD_RUST)
    include(FetchContent)
    FetchContent_Declare(Corrosion
        GIT_REPOSITORY https://github.com/corrosion-rs/corrosion.git
        GIT_TAG 1499b14e4906a2890f5cee1547c8848db261753d
    )
    FetchContent_MakeAvailable(Corrosion)
    corrosion_import_crate(MANIFEST_PATH ${CMAKE_CURRENT_SOURCE_DIR}/bindings/rust/Cargo.toml)
endif()

install(TARGETS ytdlp ytdlp_static
    EXPORT libytdlp-targets
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
install(DIRECTORY include/libytdlp DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
    PATTERN "version.h.in" EXCLUDE
)
install(
    FILES "${CMAKE_CURRENT_BINARY_DIR}/generated/include/libytdlp/version.h"
    DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/libytdlp
)
install(DIRECTORY python/libytdlp_bridge DESTINATION share/libytdlp/python
    PATTERN "__pycache__" EXCLUDE
    PATTERN "*.pyc" EXCLUDE
)
if(LIBYTDLP_BUNDLE_YTDLP)
    install(
        DIRECTORY third_party/yt-dlp/
        DESTINATION share/libytdlp/yt-dlp
        PATTERN ".git*" EXCLUDE
        PATTERN "__pycache__" EXCLUDE
        PATTERN "*.pyc" EXCLUDE
    )
endif()

install(EXPORT libytdlp-targets
    NAMESPACE libytdlp::
    DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/libytdlp
)

include(CMakePackageConfigHelpers)

file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/generated/libytdlp-config.cmake" [=[
include(CMakeFindDependencyMacro)

find_dependency(Threads)

find_dependency(Python COMPONENTS Interpreter Development.Embed)

include("${CMAKE_CURRENT_LIST_DIR}/libytdlp-targets.cmake")
]=])

write_basic_package_version_file(
    "${CMAKE_CURRENT_BINARY_DIR}/generated/libytdlp-config-version.cmake"
    VERSION ${PROJECT_VERSION}
    COMPATIBILITY SameMajorVersion
)

install(
    FILES
        "${CMAKE_CURRENT_BINARY_DIR}/generated/libytdlp-config.cmake"
        "${CMAKE_CURRENT_BINARY_DIR}/generated/libytdlp-config-version.cmake"
    DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/libytdlp
)

install(
    FILES
        "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE"
        "${CMAKE_CURRENT_SOURCE_DIR}/NOTICE"
        "${CMAKE_CURRENT_SOURCE_DIR}/THIRD_PARTY_NOTICES.md"
    DESTINATION ${CMAKE_INSTALL_DOCDIR}
)

configure_file(
    "${CMAKE_CURRENT_SOURCE_DIR}/cmake/install_sbom.cmake.in"
    "${CMAKE_CURRENT_BINARY_DIR}/generated/install_sbom.cmake"
    @ONLY
)

install(SCRIPT "${CMAKE_CURRENT_BINARY_DIR}/generated/install_sbom.cmake")

if(BUILD_TESTING)
    find_program(UV_EXECUTABLE NAMES uv REQUIRED)

    add_executable(ly_smoke tests/native/smoke.c tests/native/fixture.c)
    target_include_directories(ly_smoke PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/include
        ${CMAKE_CURRENT_BINARY_DIR}/generated/include
    )
    target_link_libraries(ly_smoke PRIVATE ytdlp Python::Python)
    if(NOT WIN32)
        target_link_libraries(ly_smoke PRIVATE Threads::Threads)
    else()
        target_link_libraries(ly_smoke PRIVATE ws2_32)
    endif()
    target_compile_definitions(ly_smoke PRIVATE
        LIBYTDLP_TEST_PYTHON="${Python_EXECUTABLE}"
        LIBYTDLP_SLOW_SERVER="${CMAKE_CURRENT_SOURCE_DIR}/tests/fixtures/slow_server.py"
        LIBYTDLP_DEPLOYMENT_PROFILE="${LIBYTDLP_DEPLOYMENT_PROFILE}"
    )

    add_executable(ly_jobs tests/native/jobs.c tests/native/fixture.c)
    target_include_directories(ly_jobs PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/include
        ${CMAKE_CURRENT_BINARY_DIR}/generated/include
    )
    target_link_libraries(ly_jobs PRIVATE ytdlp Python::Python)
    if(NOT WIN32)
        target_link_libraries(ly_jobs PRIVATE Threads::Threads)
    else()
        target_link_libraries(ly_jobs PRIVATE ws2_32)
    endif()
    target_compile_definitions(ly_jobs PRIVATE
        LIBYTDLP_TEST_PYTHON="${Python_EXECUTABLE}"
        LIBYTDLP_SLOW_SERVER="${CMAKE_CURRENT_SOURCE_DIR}/tests/fixtures/slow_server.py"
    )

    add_library(ly_header_check_c OBJECT tests/native/header_check.c)
    target_include_directories(ly_header_check_c PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/include
        ${CMAKE_CURRENT_BINARY_DIR}/generated/include
    )

    add_library(ly_header_check_cxx OBJECT tests/native/header_check.cpp)
    target_include_directories(ly_header_check_cxx PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/include
        ${CMAKE_CURRENT_BINARY_DIR}/generated/include
    )

    if(LIBYTDLP_SANITIZERS)
        target_compile_options(ly_smoke PRIVATE -fsanitize=${LIBYTDLP_SANITIZER_FLAGS}
                                                -fno-omit-frame-pointer)
        target_link_options(ly_smoke PRIVATE -fsanitize=${LIBYTDLP_SANITIZER_FLAGS})
        target_compile_options(ly_jobs PRIVATE -fsanitize=${LIBYTDLP_SANITIZER_FLAGS}
                                               -fno-omit-frame-pointer)
        target_link_options(ly_jobs PRIVATE -fsanitize=${LIBYTDLP_SANITIZER_FLAGS})
    endif()

    if(MSVC)
        foreach(target ly_smoke ly_jobs)
            target_compile_options(${target} PRIVATE
                $<$<CONFIG:Release,RelWithDebInfo,MinSizeRel>:/UNDEBUG>
            )
        endforeach()
    else()
        foreach(target ly_smoke ly_jobs)
            target_compile_options(${target} PRIVATE
                $<$<CONFIG:Release,RelWithDebInfo,MinSizeRel>:-UNDEBUG>
            )
        endforeach()
    endif()

    add_test(NAME smoke COMMAND ly_smoke)

    if(LIBYTDLP_DEPLOYMENT_PROFILE STREQUAL "SYSTEM_YTDLP_SYSTEM_PYTHON")
        set(LIBYTDLP_TEST_ENV
            "LIBYTDLP_BRIDGE_DIR=${CMAKE_CURRENT_SOURCE_DIR}/python"
        )
    else()
        set(LIBYTDLP_TEST_ENV
            "LIBYTDLP_BRIDGE_DIR=${CMAKE_CURRENT_SOURCE_DIR}/python;LIBYTDLP_YTDLP_DIR=${CMAKE_CURRENT_SOURCE_DIR}/third_party/yt-dlp"
        )
    endif()

    set_tests_properties(smoke PROPERTIES ENVIRONMENT "${LIBYTDLP_TEST_ENV}")

    add_test(NAME jobs COMMAND ly_jobs)
    set_tests_properties(jobs PROPERTIES ENVIRONMENT "${LIBYTDLP_TEST_ENV}")

    if(WIN32)
        set_tests_properties(smoke jobs PROPERTIES ENVIRONMENT_MODIFICATION
            "PATH=path_list_prepend:$<TARGET_FILE_DIR:ytdlp>"
        )
    endif()

    add_test(NAME bridge
        COMMAND ${UV_EXECUTABLE} run --project ${CMAKE_CURRENT_SOURCE_DIR} python tests/python/bridge_smoke.py
    )
    set_tests_properties(bridge PROPERTIES WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})

    set(LIBYTDLP_INSTALL_VERIFY_PREFIX ${CMAKE_CURRENT_BINARY_DIR}/install-verify/prefix)
    set(LIBYTDLP_INSTALL_VERIFY_WORK ${CMAKE_CURRENT_BINARY_DIR}/install-verify/work)

    set(LIBYTDLP_TEST_PYTHON_INCLUDES "")
    foreach(dir IN LISTS Python_INCLUDE_DIRS)
        list(APPEND LIBYTDLP_TEST_PYTHON_INCLUDES "-I${dir}")
    endforeach()

    get_filename_component(LIBYTDLP_PYTHON_LIBDIR "${Python_LIBRARIES}" DIRECTORY)

    if(NOT WIN32)
        add_test(NAME install-tree
            COMMAND ${CMAKE_COMMAND}
                -DLIBYTDLP_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR}
                -DLIBYTDLP_BUILD_DIR=${CMAKE_CURRENT_BINARY_DIR}
                -DLIBYTDLP_INSTALL_PREFIX=${LIBYTDLP_INSTALL_VERIFY_PREFIX}
                -DLIBYTDLP_INSTALL_LIBDIR=${CMAKE_INSTALL_LIBDIR}
                -DLIBYTDLP_C_COMPILER=${CMAKE_C_COMPILER}
                -DLIBYTDLP_TEST_PYTHON=${Python_EXECUTABLE}
                -DLIBYTDLP_SLOW_SERVER=${CMAKE_CURRENT_SOURCE_DIR}/tests/fixtures/slow_server.py
                -DLIBYTDLP_BRIDGE_DIR=${CMAKE_CURRENT_SOURCE_DIR}/python
                -DLIBYTDLP_YTDLP_DIR=${CMAKE_CURRENT_SOURCE_DIR}/third_party/yt-dlp
                "-DLIBYTDLP_PYTHON_INCLUDES=${LIBYTDLP_TEST_PYTHON_INCLUDES}"
                "-DLIBYTDLP_PYTHON_LIB=${Python_LIBRARIES}"
                -DLIBYTDLP_PYTHON_LIBDIR=${LIBYTDLP_PYTHON_LIBDIR}
                -DLIBYTDLP_PROJECT_VERSION=${PROJECT_VERSION}
                -DLIBYTDLP_DEPLOYMENT_PROFILE=${LIBYTDLP_DEPLOYMENT_PROFILE}
                -DLIBYTDLP_WORK_DIR=${LIBYTDLP_INSTALL_VERIFY_WORK}
                -DLIBYTDLP_BUILD_CONFIG=$<CONFIG>
                -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/install_verify.cmake
        )
    endif()
endif()