FEAT: thêm nút bypass cho track strip để bypass không qua mastering panel

This commit is contained in:
2026-08-03 15:47:10 +07:00
parent 6f55d36085
commit a9da813cb1
28 changed files with 2076 additions and 233 deletions
+993
View File
@@ -0,0 +1,993 @@
[
{
"id": "bca1bb5f-b656-48a8-b113-8fff900188ab",
"name": "Test",
"data_json": {
"project_id": "project_1784710097790",
"metadata": {
"title": "Test",
"bpm": 120.0,
"time_signature_numerator": 4,
"time_signature_denominator": 4,
"sample_rate": 44100
},
"main_session": {
"id": "main",
"name": "MAIN SESSION",
"is_root": true,
"length_bars": 16.0,
"auto_compute_length": true,
"tracks": [
{
"id": "1",
"name": "Track 01",
"type": "AUDIO",
"volume_db": 0,
"pan": 0,
"mute": false,
"solo": false,
"fx_chain": [],
"synth_engine": {
"plugin_id": "synth",
"preset_id": "default",
"parameters": {}
},
"items": []
},
{
"id": "2",
"name": "Track 02",
"type": "AUDIO",
"volume_db": 0,
"pan": 0,
"mute": false,
"solo": false,
"fx_chain": [],
"synth_engine": {
"plugin_id": "synth",
"preset_id": "default",
"parameters": {}
},
"items": []
}
]
},
"section_store": {}
},
"updated_at": 1784710143.4476569
},
{
"id": "d627f532-b632-4ef0-a21f-80dcc2ac8396",
"name": "Test",
"data_json": {
"project_id": "project_1784719724178",
"metadata": {
"title": "Test",
"bpm": 120.0,
"time_signature_numerator": 4,
"time_signature_denominator": 4,
"sample_rate": 44100
},
"main_session": {
"id": "main",
"name": "MAIN SESSION",
"is_root": true,
"length_bars": 16.0,
"auto_compute_length": true,
"tracks": [
{
"id": "1",
"name": "Track 01",
"type": "AUDIO",
"volume_db": 0,
"pan": 0,
"mute": false,
"solo": false,
"fx_chain": [],
"synth_engine": {
"plugin_id": "synth",
"preset_id": "default",
"parameters": {}
},
"items": []
},
{
"id": "2",
"name": "Track 02",
"type": "AUDIO",
"volume_db": 0,
"pan": 0,
"mute": false,
"solo": false,
"fx_chain": [],
"synth_engine": {
"plugin_id": "synth",
"preset_id": "default",
"parameters": {}
},
"items": []
}
]
},
"section_store": {}
},
"updated_at": 1784719770.23144
},
{
"id": "a17fe50b-c51d-408d-b45c-c09645777e68",
"name": "Test",
"data_json": {
"project_id": "project_1784719775936",
"metadata": {
"title": "Test",
"bpm": 120.0,
"time_signature_numerator": 4,
"time_signature_denominator": 4,
"sample_rate": 44100
},
"main_session": {
"id": "main",
"name": "MAIN SESSION",
"is_root": true,
"length_bars": 16.0,
"auto_compute_length": true,
"tracks": [
{
"id": "1",
"name": "Track 01",
"type": "AUDIO",
"volume_db": 0,
"pan": 0,
"mute": false,
"solo": false,
"fx_chain": [],
"synth_engine": {
"plugin_id": "synth",
"preset_id": "default",
"parameters": {}
},
"items": []
},
{
"id": "2",
"name": "Track 02",
"type": "AUDIO",
"volume_db": 0,
"pan": 0,
"mute": false,
"solo": false,
"fx_chain": [],
"synth_engine": {
"plugin_id": "synth",
"preset_id": "default",
"parameters": {}
},
"items": []
}
]
},
"section_store": {}
},
"updated_at": 1784719821.9856758
},
{
"id": "38a0f344-05dc-47e3-a769-07426c57f5fe",
"name": "Test",
"data_json": {
"project_id": "project_1784720540893",
"metadata": {
"title": "Test",
"bpm": 120.0,
"time_signature_numerator": 4,
"time_signature_denominator": 4,
"sample_rate": 44100
},
"main_session": {
"id": "main",
"name": "MAIN SESSION",
"is_root": true,
"length_bars": 16.0,
"auto_compute_length": true,
"tracks": [
{
"id": "1",
"name": "Cartoon Capers Loop.mp3",
"type": "AUDIO",
"volume_db": 0,
"pan": 0,
"mute": false,
"solo": false,
"fx_chain": [],
"synth_engine": {
"plugin_id": "synth",
"preset_id": "default",
"parameters": {}
},
"items": []
},
{
"id": "2",
"name": "Track 02",
"type": "AUDIO",
"volume_db": 0,
"pan": 0,
"mute": false,
"solo": false,
"fx_chain": [],
"synth_engine": {
"plugin_id": "synth",
"preset_id": "default",
"parameters": {}
},
"items": []
}
]
},
"section_store": {}
},
"updated_at": 1784720586.9746954
},
{
"id": "8037d8f5-1410-4639-8167-5b89dcb1c3e9",
"name": "Rose",
"data_json": {
"project_id": "project_1784720555735",
"metadata": {
"title": "Rose",
"bpm": 120.0,
"time_signature_numerator": 4,
"time_signature_denominator": 4,
"sample_rate": 44100
},
"main_session": {
"id": "main",
"name": "MAIN SESSION",
"is_root": true,
"length_bars": 16.0,
"auto_compute_length": true,
"tracks": [
{
"id": "1",
"name": "Track 01",
"type": "AUDIO",
"volume_db": 0,
"pan": 0,
"mute": false,
"solo": false,
"fx_chain": [],
"synth_engine": {
"plugin_id": "synth",
"preset_id": "default",
"parameters": {}
},
"items": []
},
{
"id": "2",
"name": "Track 02",
"type": "AUDIO",
"volume_db": 0,
"pan": 0,
"mute": false,
"solo": false,
"fx_chain": [],
"synth_engine": {
"plugin_id": "synth",
"preset_id": "default",
"parameters": {}
},
"items": []
}
]
},
"section_store": {}
},
"updated_at": 1784720601.8155787
},
{
"id": "525bdab5-e594-4a0c-a3e6-102a0abef816",
"name": "Rose (autosave 03/08)",
"data_json": {
"project_id": "8037d8f5-1410-4639-8167-5b89dcb1c3e9",
"metadata": {
"title": "Rose (autosave 03/08)",
"bpm": 128,
"time_signature_numerator": 4,
"time_signature_denominator": 4,
"sample_rate": 44100
},
"main_session": {
"id": "main",
"name": "MAIN SESSION",
"is_root": true,
"length_bars": 16,
"auto_compute_length": true,
"tracks": [
{
"id": "1",
"name": "Track 01",
"type": "MIDI",
"volume_db": 0,
"pan": 0,
"mute": false,
"solo": false,
"instrument_id": "sf_DSK_Asian_DreamZ",
"instrument_program": 5,
"instrument_name": "BAN-DI",
"items": [
{
"id": "midi_1785057918746",
"name": "MIDI Item",
"type": "MIDI_ITEM",
"start_bar": 0,
"duration_bars": 4,
"clip_start_offset_bars": 0,
"source_data": {
"total_buffer_bars": 4,
"notes": [
{
"id": "note_1785057946413j2a54",
"pitch": 55,
"start_beat": 1.5,
"duration_beats": 0.125,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057946488uv1f6r6q_2",
"pitch": 56,
"start_beat": 1.5166666666666666,
"duration_beats": 0.2333333333333334,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057946505gxl5rre2_3",
"pitch": 57,
"start_beat": 1.75,
"duration_beats": 0.3833333333333333,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057946522v0dmipvj_4",
"pitch": 60,
"start_beat": 2.1333333333333333,
"duration_beats": 0.2666666666666666,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_17850579465381vqpeamx_5",
"pitch": 62,
"start_beat": 2.4,
"duration_beats": 0.21666666666666679,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057946555rmpgksfp_6",
"pitch": 63,
"start_beat": 2.6166666666666667,
"duration_beats": 0.20000000000000018,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057946572cvrxri09_7",
"pitch": 64,
"start_beat": 2.816666666666667,
"duration_beats": 0.25,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057946588nvmp7zld_8",
"pitch": 66,
"start_beat": 3.066666666666667,
"duration_beats": 0.1499999999999999,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057946605jkbnh8mf_9",
"pitch": 67,
"start_beat": 3.216666666666667,
"duration_beats": 0.2666666666666666,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057946638i28svktt_10",
"pitch": 68,
"start_beat": 3.4833333333333334,
"duration_beats": 0.1499999999999999,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_17850579466559iy45ml8_11",
"pitch": 69,
"start_beat": 3.6333333333333333,
"duration_beats": 0.1333333333333333,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057946672s712j5m6_12",
"pitch": 70,
"start_beat": 3.7666666666666666,
"duration_beats": 0.1333333333333333,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057946688pq858zcx_13",
"pitch": 71,
"start_beat": 3.9,
"duration_beats": 0.18333333333333313,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_17850579467054usc79tx_14",
"pitch": 72,
"start_beat": 4.083333333333333,
"duration_beats": 0.15000000000000036,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057946722dspmlfaa_15",
"pitch": 73,
"start_beat": 4.233333333333333,
"duration_beats": 0.2999999999999998,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057946755h5mvip3h_16",
"pitch": 74,
"start_beat": 4.533333333333333,
"duration_beats": 0.15000000000000036,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057946772a5e5iona_17",
"pitch": 75,
"start_beat": 4.683333333333334,
"duration_beats": 0.16666666666666607,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057946788cfqxk2c8_18",
"pitch": 76,
"start_beat": 4.85,
"duration_beats": 0.2666666666666666,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_178505794682247xy477z_19",
"pitch": 77,
"start_beat": 5.116666666666666,
"duration_beats": 0.25,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057946855nygukqis_20",
"pitch": 78,
"start_beat": 5.366666666666666,
"duration_beats": 0.20000000000000018,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057946888oyfvmvq2_21",
"pitch": 79,
"start_beat": 5.566666666666666,
"duration_beats": 0.35000000000000053,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057947455xrq7rfmm_22",
"pitch": 78,
"start_beat": 5.916666666666667,
"duration_beats": 0.2833333333333332,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_17850579474722sn8bsed_23",
"pitch": 77,
"start_beat": 6.2,
"duration_beats": 0.31666666666666643,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057947489mbv5z7q6_24",
"pitch": 76,
"start_beat": 6.516666666666667,
"duration_beats": 0.2833333333333332,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057947505qmasllso_25",
"pitch": 75,
"start_beat": 6.8,
"duration_beats": 0.41666666666666696,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057947539wkqyr6fw_26",
"pitch": 74,
"start_beat": 7.216666666666667,
"duration_beats": 0.18333333333333357,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057947572a2ydjwzu_27",
"pitch": 73,
"start_beat": 7.4,
"duration_beats": 0.1999999999999993,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057947589b18pcser_28",
"pitch": 72,
"start_beat": 7.6,
"duration_beats": 0.16666666666666696,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057947605bsiv751r_29",
"pitch": 71,
"start_beat": 7.766666666666667,
"duration_beats": 0.13333333333333375,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057947622ii453i6x_30",
"pitch": 70,
"start_beat": 7.9,
"duration_beats": 0.18333333333333357,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057947639zdv6seea_31",
"pitch": 69,
"start_beat": 8.083333333333334,
"duration_beats": 0.36666666666666536,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057947672devle9p7_32",
"pitch": 68,
"start_beat": 8.45,
"duration_beats": 0.25,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057947689x8rgn7ih_33",
"pitch": 67,
"start_beat": 8.7,
"duration_beats": 0.3333333333333339,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057947722u5b36hrl_34",
"pitch": 66,
"start_beat": 9.033333333333333,
"duration_beats": 0.2833333333333332,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057947772f9qp4st0_35",
"pitch": 65,
"start_beat": 9.316666666666666,
"duration_beats": 0.38333333333333286,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057947805cf5bjiv1_36",
"pitch": 64,
"start_beat": 9.7,
"duration_beats": 0.6666666666666679,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057947855ojfcoccj_37",
"pitch": 63,
"start_beat": 10.366666666666667,
"duration_beats": 0.36666666666666536,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_17850579478721jvhzolj_38",
"pitch": 62,
"start_beat": 10.733333333333333,
"duration_beats": 0.3000000000000007,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057947889nqk9ckue_39",
"pitch": 61,
"start_beat": 11.033333333333333,
"duration_beats": 0.3333333333333339,
"velocity": 0.8,
"pan": 0
}
]
}
}
]
},
{
"id": "2",
"name": "Track 02",
"type": "AUDIO",
"volume_db": 0,
"pan": 0,
"mute": false,
"solo": false,
"instrument_id": null,
"instrument_program": null,
"instrument_name": null,
"items": []
}
]
},
"sub_tabs": [
{
"id": "midi_1785057919362",
"label": "Piano Roll: MIDI Item",
"type": "PIANO_ROLL",
"track_id": "1",
"target_id": "midi_1785057918746",
"parent_tab_id": null,
"notes": [
{
"id": "note_1785057946413j2a54",
"pitch": 55,
"start_beat": 1.5,
"duration_beats": 0.125,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057946488uv1f6r6q_2",
"pitch": 56,
"start_beat": 1.5166666666666666,
"duration_beats": 0.2333333333333334,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057946505gxl5rre2_3",
"pitch": 57,
"start_beat": 1.75,
"duration_beats": 0.3833333333333333,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057946522v0dmipvj_4",
"pitch": 60,
"start_beat": 2.1333333333333333,
"duration_beats": 0.2666666666666666,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_17850579465381vqpeamx_5",
"pitch": 62,
"start_beat": 2.4,
"duration_beats": 0.21666666666666679,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057946555rmpgksfp_6",
"pitch": 63,
"start_beat": 2.6166666666666667,
"duration_beats": 0.20000000000000018,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057946572cvrxri09_7",
"pitch": 64,
"start_beat": 2.816666666666667,
"duration_beats": 0.25,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057946588nvmp7zld_8",
"pitch": 66,
"start_beat": 3.066666666666667,
"duration_beats": 0.1499999999999999,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057946605jkbnh8mf_9",
"pitch": 67,
"start_beat": 3.216666666666667,
"duration_beats": 0.2666666666666666,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057946638i28svktt_10",
"pitch": 68,
"start_beat": 3.4833333333333334,
"duration_beats": 0.1499999999999999,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_17850579466559iy45ml8_11",
"pitch": 69,
"start_beat": 3.6333333333333333,
"duration_beats": 0.1333333333333333,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057946672s712j5m6_12",
"pitch": 70,
"start_beat": 3.7666666666666666,
"duration_beats": 0.1333333333333333,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057946688pq858zcx_13",
"pitch": 71,
"start_beat": 3.9,
"duration_beats": 0.18333333333333313,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_17850579467054usc79tx_14",
"pitch": 72,
"start_beat": 4.083333333333333,
"duration_beats": 0.15000000000000036,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057946722dspmlfaa_15",
"pitch": 73,
"start_beat": 4.233333333333333,
"duration_beats": 0.2999999999999998,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057946755h5mvip3h_16",
"pitch": 74,
"start_beat": 4.533333333333333,
"duration_beats": 0.15000000000000036,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057946772a5e5iona_17",
"pitch": 75,
"start_beat": 4.683333333333334,
"duration_beats": 0.16666666666666607,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057946788cfqxk2c8_18",
"pitch": 76,
"start_beat": 4.85,
"duration_beats": 0.2666666666666666,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_178505794682247xy477z_19",
"pitch": 77,
"start_beat": 5.116666666666666,
"duration_beats": 0.25,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057946855nygukqis_20",
"pitch": 78,
"start_beat": 5.366666666666666,
"duration_beats": 0.20000000000000018,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057946888oyfvmvq2_21",
"pitch": 79,
"start_beat": 5.566666666666666,
"duration_beats": 0.35000000000000053,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057947455xrq7rfmm_22",
"pitch": 78,
"start_beat": 5.916666666666667,
"duration_beats": 0.2833333333333332,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_17850579474722sn8bsed_23",
"pitch": 77,
"start_beat": 6.2,
"duration_beats": 0.31666666666666643,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057947489mbv5z7q6_24",
"pitch": 76,
"start_beat": 6.516666666666667,
"duration_beats": 0.2833333333333332,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057947505qmasllso_25",
"pitch": 75,
"start_beat": 6.8,
"duration_beats": 0.41666666666666696,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057947539wkqyr6fw_26",
"pitch": 74,
"start_beat": 7.216666666666667,
"duration_beats": 0.18333333333333357,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057947572a2ydjwzu_27",
"pitch": 73,
"start_beat": 7.4,
"duration_beats": 0.1999999999999993,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057947589b18pcser_28",
"pitch": 72,
"start_beat": 7.6,
"duration_beats": 0.16666666666666696,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057947605bsiv751r_29",
"pitch": 71,
"start_beat": 7.766666666666667,
"duration_beats": 0.13333333333333375,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057947622ii453i6x_30",
"pitch": 70,
"start_beat": 7.9,
"duration_beats": 0.18333333333333357,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057947639zdv6seea_31",
"pitch": 69,
"start_beat": 8.083333333333334,
"duration_beats": 0.36666666666666536,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057947672devle9p7_32",
"pitch": 68,
"start_beat": 8.45,
"duration_beats": 0.25,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057947689x8rgn7ih_33",
"pitch": 67,
"start_beat": 8.7,
"duration_beats": 0.3333333333333339,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057947722u5b36hrl_34",
"pitch": 66,
"start_beat": 9.033333333333333,
"duration_beats": 0.2833333333333332,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057947772f9qp4st0_35",
"pitch": 65,
"start_beat": 9.316666666666666,
"duration_beats": 0.38333333333333286,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057947805cf5bjiv1_36",
"pitch": 64,
"start_beat": 9.7,
"duration_beats": 0.6666666666666679,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057947855ojfcoccj_37",
"pitch": 63,
"start_beat": 10.366666666666667,
"duration_beats": 0.36666666666666536,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_17850579478721jvhzolj_38",
"pitch": 62,
"start_beat": 10.733333333333333,
"duration_beats": 0.3000000000000007,
"velocity": 0.8,
"pan": 0
},
{
"id": "note_1785057947889nqk9ckue_39",
"pitch": 61,
"start_beat": 11.033333333333333,
"duration_beats": 0.3333333333333339,
"velocity": 0.8,
"pan": 0
}
],
"duration": 7.5,
"instrument_program": 5,
"instrument_name": "BAN-DI",
"current_time": 0,
"color": null
}
],
"section_store": {}
},
"updated_at": 1785058087.45
}
]
+62
View File
@@ -0,0 +1,62 @@
"""Khôi phục các dự án cloud cũ (từ git history) vào bất kỳ sonicforge.db nào.
Cách dùng trên máy deployment thật (host game):
python3 tools/restore_cloud_projects.py /path/to/sonicforge.db
Script đọc backup projects (JSON) đã xuất từ git history và chèn vào DB chỉ định,
gán tất cả cho user_id được yêu cầu (mặc định: admin đầu tiên tìm thấy).
"""
import json
import os
import sqlite3
import sys
PROJECTS_BACKUP = os.path.join(os.path.dirname(__file__), "cloud_projects_backup.json")
def main(db_path: str, user_id: str = None):
if not os.path.exists(PROJECTS_BACKUP):
print(f"Không tìm thấy {PROJECTS_BACKUP}")
return 1
if not os.path.exists(db_path):
print(f"Không tìm thấy DB: {db_path}")
return 1
with open(PROJECTS_BACKUP) as f:
projects = json.load(f)
conn = sqlite3.connect(db_path)
conn.row_factory = sqlite3.Row
if user_id is None:
row = conn.execute("SELECT id FROM users WHERE role='admin' ORDER BY created_at LIMIT 1").fetchone()
if not row:
print("Không có user admin nào trong DB")
conn.close()
return 1
user_id = row["id"]
print(f"Gán tất cả cho admin: {user_id}")
restored = 0
for p in projects:
if conn.execute("SELECT id FROM projects WHERE id=?", (p["id"],)).fetchone():
print(f" bỏ qua (đã tồn tại): {p['name']}")
continue
conn.execute(
"INSERT INTO projects (id, user_id, name, data_json, is_temp, size_bytes, updated_at) VALUES (?,?,?,?,0,?,?)",
(p["id"], user_id, p["name"], json.dumps(p["data_json"], ensure_ascii=False),
len(json.dumps(p["data_json"]).encode("utf-8")), p["updated_at"]))
restored += 1
print(f" đã khôi phục: {p['name']}")
conn.commit()
conn.close()
print(f"\nHoàn tất: {restored} dự án đã khôi phục cho user {user_id}")
return 0
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python3 restore_cloud_projects.py <path/to/sonicforge.db> [user_id]")
sys.exit(1)
sys.exit(main(sys.argv[1], sys.argv[2] if len(sys.argv) > 2 else None))