|
1 | 1 | import copy |
2 | 2 | import itertools |
| 3 | +import os |
3 | 4 | import platform |
| 5 | +import sqlite3 |
4 | 6 | import sys |
5 | 7 | import threading |
6 | 8 | import time |
7 | 9 | import types |
8 | 10 |
|
9 | 11 | from queue import Queue |
| 12 | +from textwrap import dedent |
10 | 13 |
|
11 | 14 | # standard numeric/scientific libraries |
12 | 15 | import numpy as np |
@@ -3442,12 +3445,21 @@ def __init__( |
3442 | 3445 | self.ac3_processes, self.outfile_ac3 = ac3_pipe(fname_out + ".ac3") |
3443 | 3446 | self.do_rftbc = True |
3444 | 3447 |
|
| 3448 | + if os.path.exists(fname_out + '.tbc.db'): |
| 3449 | + os.unlink(fname_out + '.tbc.db') |
| 3450 | + self.dbconn = sqlite3.connect(fname_out + '.tbc.db') |
| 3451 | + self.create_db_schema() |
| 3452 | + |
3445 | 3453 | self.pipe_rftbc = extra_options.get("pipe_RF_TBC", None) |
3446 | 3454 | if self.pipe_rftbc: |
3447 | 3455 | self.do_rftbc = True |
3448 | 3456 |
|
3449 | 3457 | self.fname_out = fname_out |
3450 | 3458 |
|
| 3459 | + # TODO: set this uniquely? |
| 3460 | + self.wrote_metadata = False |
| 3461 | + self.capture_id = 1 |
| 3462 | + |
3451 | 3463 | self.firstfield = None # In frame output mode, the first field goes here |
3452 | 3464 |
|
3453 | 3465 | self.system = system |
@@ -3540,6 +3552,134 @@ def close(self): |
3540 | 3552 |
|
3541 | 3553 | self.print_stats() |
3542 | 3554 |
|
| 3555 | + def create_db_schema(self): |
| 3556 | + cur = self.dbconn.cursor() |
| 3557 | + |
| 3558 | + # Enforce foreign key constraints (SQLite default is usually OFF) |
| 3559 | + # cur.execute("PRAGMA foreign_keys = ON;") |
| 3560 | + |
| 3561 | + cur.executescript(dedent('''\ |
| 3562 | + PRAGMA user_version = 1; |
| 3563 | +
|
| 3564 | + CREATE TABLE capture ( |
| 3565 | + capture_id INTEGER PRIMARY KEY, |
| 3566 | + system TEXT NOT NULL CHECK (system IN ('NTSC','PAL','PAL_M')), |
| 3567 | + decoder TEXT NOT NULL CHECK (decoder IN ('ld-decode','vhs-decode')), |
| 3568 | + git_branch TEXT, |
| 3569 | + git_commit TEXT, |
| 3570 | + video_sample_rate REAL, |
| 3571 | + active_video_start INTEGER, |
| 3572 | + active_video_end INTEGER, |
| 3573 | + field_width INTEGER, |
| 3574 | + field_height INTEGER, |
| 3575 | + number_of_sequential_fields INTEGER, |
| 3576 | + colour_burst_start INTEGER, |
| 3577 | + colour_burst_end INTEGER, |
| 3578 | + is_mapped INTEGER CHECK (is_mapped IN (0,1)), |
| 3579 | + is_subcarrier_locked INTEGER CHECK (is_subcarrier_locked IN (0,1)), |
| 3580 | + is_widescreen INTEGER CHECK (is_widescreen IN (0,1)), |
| 3581 | + white_16b_ire INTEGER, |
| 3582 | + black_16b_ire INTEGER, |
| 3583 | + capture_notes TEXT |
| 3584 | + ); |
| 3585 | +
|
| 3586 | + CREATE TABLE pcm_audio_parameters ( |
| 3587 | + capture_id INTEGER PRIMARY KEY REFERENCES capture(capture_id) ON DELETE CASCADE, |
| 3588 | + bits INTEGER, |
| 3589 | + is_signed INTEGER CHECK (is_signed IN (0,1)), |
| 3590 | + is_little_endian INTEGER CHECK (is_little_endian IN (0,1)), |
| 3591 | + sample_rate REAL |
| 3592 | + ); |
| 3593 | +
|
| 3594 | + CREATE TABLE field_record ( |
| 3595 | + capture_id INTEGER NOT NULL REFERENCES capture(capture_id) ON DELETE CASCADE, |
| 3596 | + field_id INTEGER NOT NULL, |
| 3597 | + audio_samples INTEGER, |
| 3598 | + decode_faults INTEGER, |
| 3599 | + disk_loc REAL, |
| 3600 | + efm_t_values INTEGER, |
| 3601 | + field_phase_id INTEGER, |
| 3602 | + file_loc INTEGER, |
| 3603 | + is_first_field INTEGER CHECK (is_first_field IN (0,1)), |
| 3604 | + median_burst_ire REAL, |
| 3605 | + pad INTEGER CHECK (pad IN (0,1)), |
| 3606 | + sync_conf INTEGER, |
| 3607 | + ntsc_is_fm_code_data_valid INTEGER CHECK (ntsc_is_fm_code_data_valid IN (0,1)), |
| 3608 | + ntsc_fm_code_data INTEGER, |
| 3609 | + ntsc_field_flag INTEGER CHECK (ntsc_field_flag IN (0,1)), |
| 3610 | + ntsc_is_video_id_data_valid INTEGER CHECK (ntsc_is_video_id_data_valid IN (0,1)), |
| 3611 | + ntsc_video_id_data INTEGER, |
| 3612 | + ntsc_white_flag INTEGER CHECK (ntsc_white_flag IN (0,1)), |
| 3613 | + PRIMARY KEY (capture_id, field_id) |
| 3614 | + ); |
| 3615 | +
|
| 3616 | + CREATE TABLE vits_metrics ( |
| 3617 | + capture_id INTEGER NOT NULL, |
| 3618 | + field_id INTEGER NOT NULL, |
| 3619 | + b_psnr REAL, |
| 3620 | + w_snr REAL, |
| 3621 | + FOREIGN KEY (capture_id, field_id) |
| 3622 | + REFERENCES field_record(capture_id, field_id) ON DELETE CASCADE, |
| 3623 | + PRIMARY KEY (capture_id, field_id) |
| 3624 | + ); |
| 3625 | +
|
| 3626 | + CREATE TABLE vbi ( |
| 3627 | + capture_id INTEGER NOT NULL, |
| 3628 | + field_id INTEGER NOT NULL, |
| 3629 | + vbi0 INTEGER NOT NULL, |
| 3630 | + vbi1 INTEGER NOT NULL, |
| 3631 | + vbi2 INTEGER NOT NULL, |
| 3632 | + FOREIGN KEY (capture_id, field_id) |
| 3633 | + REFERENCES field_record(capture_id, field_id) ON DELETE CASCADE, |
| 3634 | + PRIMARY KEY (capture_id, field_id) |
| 3635 | + ); |
| 3636 | +
|
| 3637 | + CREATE TABLE drop_outs ( |
| 3638 | + capture_id INTEGER NOT NULL, |
| 3639 | + field_id INTEGER NOT NULL, |
| 3640 | + field_line INTEGER NOT NULL, |
| 3641 | + startx INTEGER NOT NULL, |
| 3642 | + endx INTEGER NOT NULL, |
| 3643 | + FOREIGN KEY (capture_id, field_id) |
| 3644 | + REFERENCES field_record(capture_id, field_id) ON DELETE CASCADE, |
| 3645 | + PRIMARY KEY (capture_id, field_id, field_line, startx, endx) |
| 3646 | + ); |
| 3647 | +
|
| 3648 | + CREATE TABLE vitc ( |
| 3649 | + capture_id INTEGER NOT NULL, |
| 3650 | + field_id INTEGER NOT NULL, |
| 3651 | + vitc0 INTEGER NOT NULL, |
| 3652 | + vitc1 INTEGER NOT NULL, |
| 3653 | + vitc2 INTEGER NOT NULL, |
| 3654 | + vitc3 INTEGER NOT NULL, |
| 3655 | + vitc4 INTEGER NOT NULL, |
| 3656 | + vitc5 INTEGER NOT NULL, |
| 3657 | + vitc6 INTEGER NOT NULL, |
| 3658 | + vitc7 INTEGER NOT NULL, |
| 3659 | + FOREIGN KEY (capture_id, field_id) |
| 3660 | + REFERENCES field_record(capture_id, field_id) ON DELETE CASCADE, |
| 3661 | + PRIMARY KEY (capture_id, field_id) |
| 3662 | + ); |
| 3663 | +
|
| 3664 | + CREATE TABLE closed_caption ( |
| 3665 | + capture_id INTEGER NOT NULL, |
| 3666 | + field_id INTEGER NOT NULL, |
| 3667 | + data0 INTEGER, |
| 3668 | + data1 INTEGER, |
| 3669 | + FOREIGN KEY (capture_id, field_id) |
| 3670 | + REFERENCES field_record(capture_id, field_id) ON DELETE CASCADE, |
| 3671 | + PRIMARY KEY (capture_id, field_id) |
| 3672 | + ); |
| 3673 | + ''')) |
| 3674 | + |
| 3675 | + self.dbconn.commit() |
| 3676 | + |
| 3677 | + cur.execute("SELECT name FROM sqlite_master WHERE type='table';") |
| 3678 | + tables = cur.fetchall() |
| 3679 | + #print([table[0] for table in tables]) |
| 3680 | + |
| 3681 | + cur.close() |
| 3682 | + |
3543 | 3683 | def roughseek(self, location, isField=True): |
3544 | 3684 | self.prevPhaseID = None |
3545 | 3685 |
|
@@ -3635,6 +3775,51 @@ def writeout(self, dataset): |
3635 | 3775 |
|
3636 | 3776 | self.fieldinfo.append(fi) |
3637 | 3777 |
|
| 3778 | + c_id = self.capture_id |
| 3779 | + f_id = fi['seqNo'] - 1 |
| 3780 | + print(c_id, f_id) |
| 3781 | + |
| 3782 | + # Insert parent record into 'field_record' |
| 3783 | + # We cast booleans to int because of the CHECK (val IN (0,1)) constraint |
| 3784 | + self.dbconn.execute(''' |
| 3785 | + INSERT INTO field_record ( |
| 3786 | + capture_id, field_id, is_first_field, sync_conf, disk_loc, |
| 3787 | + file_loc, median_burst_ire, field_phase_id, decode_faults, |
| 3788 | + audio_samples, efm_t_values |
| 3789 | + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''', |
| 3790 | + (c_id, f_id, int(fi['isFirstField']), fi['syncConf'], fi['diskLoc'], |
| 3791 | + fi['fileLoc'], fi['medianBurstIRE'], fi['fieldPhaseID'], fi['decodeFaults'], |
| 3792 | + fi['audioSamples'], fi['efmTValues'])) |
| 3793 | + |
| 3794 | + w_snr = fi['vitsMetrics'].get('wSNR', -1) |
| 3795 | + b_psnr = fi['vitsMetrics'].get('bPSNR', -1) |
| 3796 | + |
| 3797 | + self.dbconn.execute(''' |
| 3798 | + INSERT INTO vits_metrics ( |
| 3799 | + capture_id, field_id, w_snr, b_psnr |
| 3800 | + ) VALUES (?, ?, ?, ?)''', |
| 3801 | + (c_id, f_id, w_snr, b_psnr)) |
| 3802 | + |
| 3803 | + # Insert dropouts (if any) into 'drop_outs' |
| 3804 | + if self.doDOD and fi.get("dropOuts"): |
| 3805 | + dropout_lines = fi["dropOuts"]["fieldLine"] |
| 3806 | + dropout_starts = fi["dropOuts"]["startx"] |
| 3807 | + dropout_ends = fi["dropOuts"]["endx"] |
| 3808 | + |
| 3809 | + # Use executemany for cleaner/faster insertion of multiple rows |
| 3810 | + dropout_data = [ |
| 3811 | + (c_id, f_id, line, start, end) |
| 3812 | + for line, start, end in zip(dropout_lines, dropout_starts, dropout_ends) |
| 3813 | + ] |
| 3814 | + |
| 3815 | + self.dbconn.executemany(''' |
| 3816 | + INSERT INTO drop_outs ( |
| 3817 | + capture_id, field_id, field_line, startx, endx |
| 3818 | + ) VALUES (?, ?, ?, ?, ?)''', |
| 3819 | + dropout_data) |
| 3820 | + |
| 3821 | + self.dbconn.commit() |
| 3822 | + |
3638 | 3823 | self.outfile_video.write(picture) |
3639 | 3824 | self.fields_written += 1 |
3640 | 3825 |
|
@@ -4322,14 +4507,17 @@ def seek(self, startframe, target): |
4322 | 4507 |
|
4323 | 4508 | def build_json(self): |
4324 | 4509 | """ build up the JSON structure for file output. """ |
4325 | | - jout = {} |
4326 | | - jout["pcmAudioParameters"] = { |
| 4510 | + pcmAudioParameters = { |
4327 | 4511 | "bits": 16, |
4328 | 4512 | "isLittleEndian": True, |
4329 | 4513 | "isSigned": True, |
4330 | 4514 | "sampleRate": self.analog_audio, |
4331 | 4515 | } |
4332 | 4516 |
|
| 4517 | + jout = {} |
| 4518 | + |
| 4519 | + jout["pcmAudioParameters"] = pcmAudioParameters |
| 4520 | + |
4333 | 4521 | vp = {} |
4334 | 4522 |
|
4335 | 4523 | vp["numberOfSequentialFields"] = len(self.fieldinfo) |
@@ -4374,4 +4562,46 @@ def build_json(self): |
4374 | 4562 |
|
4375 | 4563 | jout["videoParameters"] = vp |
4376 | 4564 |
|
| 4565 | + if not self.wrote_metadata: |
| 4566 | + self.wrote_metadata = True |
| 4567 | + |
| 4568 | + cursor = self.dbconn.cursor() |
| 4569 | + decoder_val = vp.get('decoder', 'ld-decode') |
| 4570 | + |
| 4571 | + cursor.execute(""" |
| 4572 | + INSERT INTO capture ( |
| 4573 | + system, decoder, git_branch, git_commit, |
| 4574 | + video_sample_rate, active_video_start, active_video_end, |
| 4575 | + field_width, field_height, number_of_sequential_fields, |
| 4576 | + colour_burst_start, colour_burst_end, |
| 4577 | + white_16b_ire, black_16b_ire |
| 4578 | + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) |
| 4579 | + """, ( |
| 4580 | + vp["system"], decoder_val, vp["gitBranch"], vp["gitCommit"], |
| 4581 | + vp["sampleRate"], vp["activeVideoStart"], vp["activeVideoEnd"], |
| 4582 | + vp["fieldWidth"], vp["fieldHeight"], vp["numberOfSequentialFields"], |
| 4583 | + vp["colourBurstStart"], vp["colourBurstEnd"], |
| 4584 | + vp["white16bIre"], vp["black16bIre"] |
| 4585 | + )) |
| 4586 | + |
| 4587 | + self.capture_id = cursor.lastrowid |
| 4588 | + |
| 4589 | + # 2. Insert into 'pcm_audio_parameters' |
| 4590 | + # Now requires capture_id as a Foreign Key. |
| 4591 | + # Booleans are cast to int() to satisfy CHECK(x IN (0,1)) |
| 4592 | + cursor.execute(""" |
| 4593 | + INSERT INTO pcm_audio_parameters ( |
| 4594 | + capture_id, bits, is_little_endian, is_signed, sample_rate |
| 4595 | + ) VALUES (?, ?, ?, ?, ?) |
| 4596 | + """, ( |
| 4597 | + self.capture_id, |
| 4598 | + pcmAudioParameters["bits"], |
| 4599 | + int(pcmAudioParameters["isLittleEndian"]), |
| 4600 | + int(pcmAudioParameters["isSigned"]), |
| 4601 | + pcmAudioParameters["sampleRate"] |
| 4602 | + )) |
| 4603 | + |
| 4604 | + self.dbconn.commit() |
| 4605 | + cursor.close() |
| 4606 | + |
4377 | 4607 | return jout |
0 commit comments