diff --git a/README.md b/README.md index 3f72067..bf5d4e9 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,10 @@ This app isn't finished yet, so don't set high expectations. Fuji's implementati - Liveview & Remote capture - Implement Bluetooth pairing +- get D185 prop PTP comms from more cameras to reverse-engineer the protocol better +- implement generating FP2 profile file from the RAW file, so you can start with edits +- Replacement of X-RAW Studio for desktop + ## Compiling ``` git clone https://github.com/petabyt/fudge.git --depth 1 --recurse-submodules diff --git a/desktop/backend.c b/desktop/backend.c index 8ad5ded..1250a4d 100644 --- a/desktop/backend.c +++ b/desktop/backend.c @@ -221,7 +221,7 @@ int fudge_dump_usb(int devnum) { return rc; } -int fudge_process_raf(int devnum, const char *input, const char *output, const char *profile) { +int fudge_convert_raf(int devnum, const char *input, const char *output, const char *profile, const enum ConversionOutputQuality quality) { struct PtpRuntime *r = ptp_new(PTP_USB); int rc = fudge_usb_connect(r, devnum); if (rc) return rc; @@ -229,7 +229,8 @@ int fudge_process_raf(int devnum, const char *input, const char *output, const c rc = fujiusb_setup(r); if (rc) return rc; - rc = fuji_process_raf(r, input, output, profile); + + rc = fuji_convert_raf(r, input, output, profile, quality); ptp_close_session(r); ptp_device_close(r); diff --git a/desktop/desktop.h b/desktop/desktop.h index 7e99100..15dba91 100644 --- a/desktop/desktop.h +++ b/desktop/desktop.h @@ -3,6 +3,7 @@ #define DESKTOP_H #include #include +#include // for ConversionOutputQuality int fudge_test_all_cameras(void); @@ -27,8 +28,8 @@ int fuji_test_discovery(struct PtpRuntime *r); //int fuji_test_filesystem(struct PtpRuntime *r); //int fuji_test_setup(struct PtpRuntime *r); -/// @brief CLI function to do quick conversion -int fudge_process_raf(int devnum, const char *input, const char *output, const char *profile); +/// @brief CLI function to do a RAW file conversion into image file +int fudge_convert_raf(int devnum, const char *input, const char *output, const char *profile, const enum ConversionOutputQuality); int fudge_download_backup(int devnum, const char *output); int fudge_restore_backup(int devnum, const char *output); diff --git a/desktop/main.c b/desktop/main.c index f52024b..c0105f2 100644 --- a/desktop/main.c +++ b/desktop/main.c @@ -148,17 +148,17 @@ int main(int argc, char **argv) { printf("Error parsing profile: '%s'\n", fp_get_error()); return rc; } - fp_dump_struct(stdout, &fp); - printf("\n"); - - return 0; + return fp_dump_struct(stdout, FP_FORMAT_HUMAN_READABLE, &fp); } if (!strcmp(argv[i], "--raw")) { if (i + 3 >= argc) { printf("%s --raw \n", argv[0]); return -1; } - return fudge_process_raf(devnum, argv[i + 1], argv[i + 2], argv[i + 3]); + int rc = fudge_convert_raf(devnum, argv[i + 1], argv[i + 2], argv[i + 3], CONVERSION_OUTPUT_QUALITY_FULL); + plat_dbg("Result: %d '%s' (PTP meaning: '%s')\n", rc, fp_get_error(), ptp_perror(rc)); + + return rc; } if (!strcmp(argv[i], "--test-wifi")) { int rc = fudge_test_all_cameras(); diff --git a/desktop/ui.c b/desktop/ui.c index 7157d8d..b22b03b 100644 --- a/desktop/ui.c +++ b/desktop/ui.c @@ -137,7 +137,7 @@ static void option(const char *name, struct FujiLookup *tbl, int *option, uint32 static void *thread_raw_conversion(void *arg) { struct State *state = (struct State *)arg; - int rc = fudge_process_raf(0, state->raf_path, state->output_jpg_path, state->fp_xml_path); + int rc = fudge_convert_raf(0, state->raf_path, state->output_jpg_path, state->fp_xml_path, CONVERSION_OUTPUT_QUALITY_FULL); if (rc) { app_print("Failed to convert RAW"); } diff --git a/lib/fp b/lib/fp index 0d0ba3d..fc9599a 160000 --- a/lib/fp +++ b/lib/fp @@ -1 +1 @@ -Subproject commit 0d0ba3dba2807f3b8339914b3be4e8ba5ccb795f +Subproject commit fc9599ab826aa7af9458c0021c1a321e6713f03c diff --git a/lib/fuji.h b/lib/fuji.h index 65cec4e..aa71174 100644 --- a/lib/fuji.h +++ b/lib/fuji.h @@ -2,9 +2,9 @@ // Fudge implementations and app logic #ifndef FUJIAPP_FUJI_H #define FUJIAPP_FUJI_H -#include -#include "fujiptp.h" #include "app.h" +#include "fujiptp.h" +#include #define DEVICE_NAME "Fudge" @@ -41,6 +41,12 @@ enum DiscoverRet { FUJI_D_INVALID_NETWORK = 6, }; +enum ConversionOutputQuality { + CONVERSION_OUTPUT_QUALITY_THUMBNAIL, + CONVERSION_OUTPUT_QUALITY_PREVIEW, + CONVERSION_OUTPUT_QUALITY_FULL, +}; + /// @brief Holds all information about a camera that has been detected (through any means) struct DiscoverInfo { enum FujiTransport transport; @@ -201,8 +207,16 @@ int fuji_send_object_info_ex(struct PtpRuntime *r, int storage_id, int handle, s /// @brief Function for 0x900d int fuji_send_object_ex(struct PtpRuntime *r, const void *data, size_t length); -/// @param input_raf_path Path for RAF file +/// @brief Upload the RAW file to the camera and get it's conversion profile +/// @param input_raf_path Path for RAW file +/// @param [out] profile in d185 format +/// @param [out] profile_len length of the d185 profile +int fuji_upload_raf_get_profile(struct PtpRuntime* r, const char* input_raf_path, void** profile, int * const profile_len); + +/// @brief Do a conversion of a RAW file according to the profile file using connected camera +/// @param input_raf_path Path for RAW file /// @param profile_xml_path String data for XML profile to be parsed by fp -int fuji_process_raf(struct PtpRuntime *r, const char *input_raf_path, const char *output_path, const char *profile_xml_path); +/// @param output_path Path for the resulting JPG file +int fuji_convert_raf(struct PtpRuntime* r, const char* input_raf_path, const char* output_path, const char* profile_xml_path, const enum ConversionOutputQuality quality); #endif diff --git a/lib/fuji_usb.c b/lib/fuji_usb.c index 8aaddc4..50bf653 100644 --- a/lib/fuji_usb.c +++ b/lib/fuji_usb.c @@ -47,7 +47,7 @@ int fuji_send_object_ex(struct PtpRuntime *r, const void *data, size_t length) { struct PtpCommand cmd; cmd.code = PTP_OC_FUJI_SendObject2; cmd.param_length = 0; - return ptp_send_data(r, &cmd, data, (int)length); + return ptp_send_data(r, &cmd, data, length); } int fujiusb_dump_info(struct PtpRuntime *r) { @@ -315,60 +315,94 @@ int fuji_send_raf(struct PtpRuntime *r, const char *path) { return rc; } - r->max_packet_size = 261632; - rc = fuji_send_object_ex(r, buffer, file_size); - r->max_packet_size = 512; - free(buffer); return rc; } -int fuji_process_raf(struct PtpRuntime *r, const char *input_raf_path, const char *output_path, const char *profile_xml_path) { - int rc; - - struct FujiDeviceKnowledge *fuji = fuji_get(r); - if (fuji->transport != FUJI_FEATURE_RAW_CONV) { - ptp_error_log("Not in raw transfer mode\n"); - return PTP_RUNTIME_ERR; - } +int fuji_upload_raf_get_profile(struct PtpRuntime* r, const char* input_raf_path, void** profile, int * const profile_len) +{ + // observed that Fuji uses this packet size during RAW conversion + r->max_packet_size = FUJI_MAX_PARTIAL_OBJECT; + + int rc; + struct FujiDeviceKnowledge *fuji = fuji_get(r); + if (fuji->transport != FUJI_FEATURE_RAW_CONV) { + ptp_error_log("Not in raw transfer mode\n"); + return PTP_RUNTIME_ERR; + } + + rc = fuji_send_raf(r, input_raf_path); + if (rc) + return rc; + + // Download the profile + ptp_mutex_lock(r); + rc = ptp_get_prop_value(r, PTP_DPC_FUJI_RawConvProfile); + if (rc) { + ptp_mutex_unlock(r); + return rc; + } + *profile_len = ptp_get_payload_length(r); + *profile = malloc(*profile_len); + memcpy(*profile, ptp_get_payload(r), *profile_len); + ptp_mutex_unlock(r); + + ptp_verbose_log("Got %d bytes of profile embedded in the RAW file\n", profile_len); + + return rc; +} - rc = fuji_send_raf(r, input_raf_path); - if (rc) return rc; +int fuji_convert_raf(struct PtpRuntime *r, const char *input_raf_path, const char *output_path, const char *profile_xml_path, const enum ConversionOutputQuality quality) { + int rc; - // Download the profile - ptp_mutex_lock(r); - rc = ptp_get_prop_value(r, PTP_DPC_FUJI_RawConvProfile); + int profile_len; + void *profile; + + rc = fuji_upload_raf_get_profile(r, input_raf_path, &profile, &profile_len); if (rc) { - ptp_mutex_unlock(r); - return rc; - } - int profile_len = ptp_get_payload_length(r); - void *profile = malloc(profile_len); - memcpy(profile, ptp_get_payload(r), profile_len); - ptp_mutex_unlock(r); - ptp_verbose_log("Got %d bytes of profile\n", profile_len); + } uint8_t buffer[1024]; struct FujiProfile fp; - fp_parse_d185(profile, profile_len, &fp); + rc = fp_parse_d185(profile, profile_len, &fp); + free(profile); - struct FujiProfile user_fp; - rc = fp_parse_fp1(profile_xml_path, &user_fp); if (rc == 0) { - rc = fp_apply_profile(&user_fp, &fp); - if (rc) { - ptp_error_log("Failed to merge profile\n"); - return PTP_RUNTIME_ERR; + printf("-----\nRAF file FP profile:\n"); + rc = fp_dump_struct(stdout, FP_FORMAT_HUMAN_READABLE, &fp); + if (rc == 0) { + struct FujiProfile user_fp; + rc = fp_parse_fp1(profile_xml_path, &user_fp); + + if (rc == 0) { + rc = fp_merge_profile(&user_fp, &fp); + if (rc) { + ptp_error_log("Failed to merge profile\n"); + return PTP_RUNTIME_ERR; + } + printf("-----\nMerged FP profiles:\n"); + rc = fp_dump_struct(stdout, FP_FORMAT_HUMAN_READABLE, &fp); + if (rc) { + ptp_error_log("Failed to print/dump merged profile\n"); + return PTP_RUNTIME_ERR; + } + } else { + ptp_error_log("Failed to parse '%s', check console output\n", profile_xml_path); + return PTP_RUNTIME_ERR; + } + } else { + ptp_error_log("Failed to parse/dump RAF file profile\n"); + return PTP_RUNTIME_ERR; } } else { - ptp_error_log("Failed to parse '%s', check console output\n", profile_xml_path); + ptp_error_log("Failed to parse RAF file profile\n"); return PTP_RUNTIME_ERR; } - profile_len = fp_create_d185(&fp, buffer, sizeof(buffer)); + profile_len = fp_create_d185(&fp, (struct D185_t* )buffer, sizeof(buffer)); if (profile_len < 0) { printf("Error creating d185\n"); return -1; @@ -377,40 +411,80 @@ int fuji_process_raf(struct PtpRuntime *r, const char *input_raf_path, const cha rc = ptp_set_prop_value_data(r, PTP_DPC_FUJI_RawConvProfile, buffer, profile_len); if (rc) return rc; - free(profile); - - rc = ptp_set_prop_value16(r, PTP_DPC_FUJI_StartRawConversion, 0); + switch (quality) { + case CONVERSION_OUTPUT_QUALITY_THUMBNAIL: + case CONVERSION_OUTPUT_QUALITY_PREVIEW: + rc = ptp_set_prop_value16(r, PTP_DPC_FUJI_StartRawConversion, FUJI_START_RAW_CONVERSION_PREVIEW_CONV); + break; + case CONVERSION_OUTPUT_QUALITY_FULL: + rc = ptp_set_prop_value16(r, PTP_DPC_FUJI_StartRawConversion, FUJI_START_RAW_CONVERSION_FULL_CONV); + break; + } + if (rc) return rc; - for (int i = 0; i < 20; i++) { + for (int i = 0; i < 50; i++) { struct PtpArray *list; rc = ptp_get_object_handles(r, -1, 0, 0, &list); if (rc) return rc; if (list->length == 0) { + // wait for the converted file to be ready printf("Waiting..\n"); - usleep(1000 * 1000); + usleep(1000 * 100); free(list); + continue; } else { - rc = ptp_get_object(r, (int) list->data[0]); + // X-RAW Studio here does a call to ptp_get_object_info but it's not necessary + switch (quality) { + case CONVERSION_OUTPUT_QUALITY_THUMBNAIL: + rc = ptp_get_thumbnail(r, (int)list->data[0]); + break; + case CONVERSION_OUTPUT_QUALITY_PREVIEW: + case CONVERSION_OUTPUT_QUALITY_FULL: + rc = ptp_get_object(r, (int)list->data[0]); + break; + } + if (rc) return rc; FILE *f = fopen(output_path, "wb"); if (f == NULL) { ptp_error_log("Failed to write to output"); + free(list); return PTP_RUNTIME_ERR; } fwrite(ptp_get_payload(r), 1, ptp_get_payload_length(r), f); fclose(f); - + + // file delete is required, otherwise the camera hangs-up (LED blinks green-orange) rc = ptp_delete_object(r, (int)list->data[0]); - if (rc) return rc; - free(list); + if (rc) return rc; break; } } + // Download final the profile, used for the output file + ptp_mutex_lock(r); + rc = ptp_get_prop_value(r, PTP_DPC_FUJI_RawConvProfile); + if (rc) { + ptp_mutex_unlock(r); + return rc; + } + profile_len = ptp_get_payload_length(r); + profile = malloc(profile_len); + memcpy(profile, ptp_get_payload(r), profile_len); + ptp_mutex_unlock(r); + + ptp_verbose_log("Got %d bytes of applied profile, used for conversion\n", profile_len); + + rc = fp_parse_d185(profile, profile_len, &fp); + free(profile); + printf("-----\nApplied FP profile:\n"); + rc = fp_dump_struct(stdout, FP_FORMAT_HUMAN_READABLE, &fp); + if (rc) return rc; + return 0; } diff --git a/lib/fujiptp.h b/lib/fujiptp.h index 731c4ad..621db54 100644 --- a/lib/fujiptp.h +++ b/lib/fujiptp.h @@ -154,6 +154,17 @@ enum FujiUSBModes { FUJI_USB_MODE_WEBCAM = 8, }; +/// @brief Possible value for PTP_DPC_FUJI_StartRawConversion +enum FujiStartRawConversion { + // ignores ImageSize, Image Quality from RawConvProfile operation. Produces images smaller than S, Normal. + // Used by X-RAW Studio for "Preview: Conversion Speed Priority" + FUJI_START_RAW_CONVERSION_PREVIEW_CONV = 0, + // respects ImageSize, Image Quality from RawConvProfile. + // Used by X-RAW Studio for "Preview: Image Quality Priority" as well as the conversion itself + // ("Preview: Image Quality Priority" uses your ImageSize settings, but sets ImageQuality to "Normal") + FUJI_START_RAW_CONVERSION_FULL_CONV = 1, +}; + // ECs and PCs stuff from libgphoto2 ptp.h - most appear to be inaccurate or misplaced #define PTP_EC_FUJI_PreviewAvailable 0xC001 #define PTP_EC_FUJI_ObjectAdded 0xC004 diff --git a/lib/libpict b/lib/libpict index 8a10673..06377df 160000 --- a/lib/libpict +++ b/lib/libpict @@ -1 +1 @@ -Subproject commit 8a106733f49e286c4ef4d251bbc0ad41a9902569 +Subproject commit 06377df80e68936f90b6fe649bd85e44692ec6c4