#!/usr/bin/perl -w
# dvdlist.pl, a perl CGI script to display a list of DVDs on a web page
# Copyright (C) 2005-2023  Christian Wolff
# 
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
# Or, download it from http://www.gnu.org/copyleft/gpl.html
#
# You can reach the author via email: sub-gpl <at> scara.com
#

# Format of the DB file:
# - One line per item
# - Entries are separated by tabs (ASCII 0x09, \t)
# - First line contains the column titles, also tab-separated:
# Title	Release	Duration	Aspect ratio	Discs	Std./Reg.	Language / Audio Format	Subtitles	Info / IMDB	Date Added	Cost	Amazon ASIN	Release URL
# - Sections (e.g., for genres) are headed by a separator line starting with two dashes and a space ('-- '), followed by the section title:
# -- Action
# - Sections are listed as jump points at every section start, if the list is unsorted
# - These are the columns by index:
#   - [0] Title (name of the movie, or show and season)
#       Optionally, end the title with the release year, in parenthesis, e.g., ' (1968)'
#       Separate alternative or original titles with a slash: ' / '
#       Separate additional info, such as season or a qualifier, with a dash: ' - '. Use a colon (': ') to replace dashes in titles.
#		Start line with '###' to mark adult movies. These are skipped unless the option 'xxx=1' is specified in the URL
#		Prepend a title with 'P: ' to mark it as a pre-order, to keep track of future arrivals
#       To combine both, use '### P: '
#   - [1] Release details (such as Distributor, Special Edition, etc.)
#		Special handling to guess release link (see [12]) for details that start with these formats:
#		'criterion <spine number>'
#		'Vinegar Syndrome <SKU>' 
#   - [2] Running Time (H:MM or minutes)
#   - [3] Aspect Ratio(s), separated by '/', e.g. 1.33, 1.78, 1.37, 1.66, 1.85, 2.35, each followed by:
#       's' for standard definition (default, if no letter)
#       'a' for anamorphic widescreen
#       'h' for HighDefinition 1080p
#       'p' for 720p
#       'w' for 1080p 21:9
#       'i' for 1080i
#       'u' for UltraHD 2160p, optionally followed by HDR format:
#       'us' for SDR/Bt.2020 (default, if no second letter)
#       'uh' for HDR10
#       'ud' for HDR10 / Dolby Vision
#       'up' for HDR10 / HDR10+
#       'ut' for Technicolor HDR
#       'ug' for HLG;
#   - [4] Number of Discs, followed by Type (optional, default DVD):
#       'D' for DVD
#       'R' for DVD-R / MoD
#       'O' for DVD Audio
#       'C' for CD
#       'S' for SuperAudioCD
#       'B' for Blu-ray
#       'T' for Blu-ray 3D
#       'W' for Blu-ray Writeable / MoD
#       'U' for UltraHD Blu-ray
#       'H' for HD-DVD
#       'L' for Laser Disc
#       'M' for Video CD
#       'V' for VHS Tape
#       'X' for CD/DVD/Blu-ROM
#       'A' for UltraViolet/MoviesAnywhere
#       'I' for iTunes
#       'Y' for other digital copy
#   - [5] Standard/Region (N or P for NTSC/60Hz or PAL/50Hz, followed by list of regions(0=all, 1/A=northamerica, 2/B=europe&japan, etc.), '?' if unknown, or '-' if copy)
#   - [6] Audio Track Language(s). Each followed optionally by sound format, and additional info separated by a space (' '): 
#       [XX][n.m[.h]][ addl.info]
#       XX: see below; n=floor speaker, m=subs, h=height
#       PC=PCM
#       DD=Dolby Digital (AC3)
#       DP=DD-Plus (E-AC-3)
#       AD=DD-Plus Atmos
#       TH=TrueHD
#       AT=Dolby TrueHD Atmos
#       DT=DTS
#       HR=DTS-HD High Resolution
#       MA=DTS-HD Master Audio
#       DX=DTS:X
#       DH=DTS Headphone:X
#       AU=Auro3D
#       MH=MPEG-H 3D Audio
#       AA=Advanced Audio Coding (AAC)
#   - [7] Subtitle Language(s). Each followed optionally by type: ([addl.info]), addl.info: SDH, etc.
#     Audio and Subtitle Track Language format:
#     - Multiple tracks are separated by '/'
#     - A track can have a description (e.g. audio codec for audio track, SDH for subtitles), following in parenthesis, separated by an optional space
#     - Multiple tracks can share one description if concatenated by ',' ahead of description
#       - Each description starting with 2 uppercase characters will have those two matched against the audio codec list (see sound format above) and expanded
#     - Each track can contain multiple languages, separated by '+'
#       - Each language can have an optional region appended, separated by '-' (see RFC BCP-47)
#       - Each region consisting of 2 characters will be matched to the ISO-3166 country codes and expanded
#       - Each language consisting of 2 or 3 characters will be matched to the ISO-639 language codes and expanded
#     Examples:
#       Multiple language tracks (English, French and Spanish): en/fr/es
#       Single track with multiple languages (both French and Italian): fr+it
#       Language with regional specifier (Brazilan Portugese): pt-BR
#       Language track with description (English Subtitles for the Deaf and Hard-of-hearing): en(SDH)
#       Audio track with audio coding information (English Dolby Digital 5.1): en(DD5.1)
#       Audio track with combined audio coding information and description (English PCM, descriptive audio): en(PC Descr.)
#       Multiple tracks with shared description (Mandarin and Cantonese, each as PCM 2.0): cmn,yue(PC2.0)
#       Combination: en(MA7.1)/en(DD2.0 Commentary)/fr,es,pt-BR(DD2.0)/hy+az+ka(PC1.0)
#   - [8] URL to info about the content (movie, show, etc.): IMDB, or special site for that content
#       For IMDB title, character, or company entries, only the tags are needed ('tt', 'ch', or 'co', followed by 7 to 8 digits)
#		If none given, a search link to IMDB is generated
#   - [9] date (ISO 8601 format: YYYY-MM-DD, for sorting, e.g. aquisition date)
#   - [10] cost (i.e. how much was spent to acquire this item)
#   - [11] Retailer (For Amazon: ASIN (Product ID number, starting w/ "B0"), prepended by country and a slash ('us/', 'de/', etc.) Default: US.)
#   - [12] URL to info about the disk release (e.g. on blu-ray.com)
#		If none given, or guessed (see [0]), a search link to blu-ray.com is generated

# URL controls:
#  -- Data set selection:
#  file=FILENAME.EXT: Use database file other than dvd_list.txt ('file=' is optional if only parameter)
#  -- Content filtering, default off:
#  all=1: Show all items, including copied ones (with region = '-')
#  xxx=1: Show adult titles (lines starting with ###)
#  cost=1: Show cost of each item, and summary at the end
#  -- Sorting and filtering
#  sort=COLUMN: Sort entries by colum, 0..11
#  revsort=1: Sort in descending order, instead of ascending
#  filter=DISCTYPES: Show only items matching one of the disc types, e.g. 'filter=tb' shows all Blu-rays and Blu-ray 3D
#  section=SECTION: Show only items from the given SECTION (The heading text from a line starting with '--')
#  limit=N: Show only the first N entries, summarize the remainder
#  -- Output format
#  raw=1: Display plain text, without links
# 


use URI::Escape qw(uri_escape uri_unescape); # %xx codes
use HTML::Entities qw(encode_entities decode_entities); # &nnn; codes


# URI unescape, in place: 
#   $string =~ s/+/ /g;
# 	$string =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
# URI escape, in place:
#	$search =~ s/([^A-Za-z0-9\-\._~ ])/sprintf("%%%02X",ord($1))/eg;
#	$search =~ s/ /+/g;

#
# Global variables
#

# path to the DB file, if not in same directory as script
my $dbpath = '';

# User query from HTTP
my ($query, %query) = ('', ());
my $limit = 0;
my $filter = '';

# Expansion pattern for languages through ISO-639 Language Codes (2 and 3 letters)
# Use 'iso-639-3_download.sh' to download most recent ISO 639-3 table
#my $iso639_table = 'iso-639-3_Latin1.tab'; # Windows-1252 Latin 1
my $iso639_table = 'iso-639-3.tab'; # UTF8
my $iso639_url = 'https://scarabaeus.org/' . $iso639_table;
# ISO-639 tables at https://iso639-3.sil.org/code_tables/download_tables
# - UTF8:   https://iso639-3.sil.org/sites/iso639-3/files/downloads/iso-639-3.tab
# - Latin1: https://iso639-3.sil.org/sites/iso639-3/files/downloads/iso-639-3_Latin1.tab
# tab-delimited:
#         Id      char(3) NOT NULL,  -- The three-letter 639-3 identifier
#         Part2B  char(3) NULL,      -- Equivalent 639-2 identifier of the bibliographic applications 
#                                    -- code set, if there is one
#         Part2T  char(3) NULL,      -- Equivalent 639-2 identifier of the terminology applications code 
#                                    -- set, if there is one
#         Part1   char(2) NULL,      -- Equivalent 639-1 identifier, if there is one    
#         Scope   char(1) NOT NULL,  -- I(ndividual), M(acrolanguage), S(pecial)
#         Type    char(1) NOT NULL,  -- A(ncient), C(onstructed),  
#                                    -- E(xtinct), H(istorical), L(iving), S(pecial)
#         Ref_Name   varchar(150) NOT NULL,   -- Reference language name 
#         Comment    varchar(150) NULL)       -- Comment relating to one or more of the columns

# Parse file of ISO-639 codes into hash table
my %lang = ();
if (open LANG, "<${iso639_table}") {
	my $header = 1;
	while (<LANG>) {
		chomp;
		my @line = split "\t";
		#print "ID:$line[0], 2B:$line[1], 2T:$line[2], 1:$line[3], Scope:$line[4], Type:$line[5], Name:$line[6], Comment:$line[7]<br>\n";
		if ($header) {
			$header = 0; # Skip header line
		} else {
			$line[6] =~ s/\s*\(.*\)//;
			$lang{$line[3]} = $line[6] if $line[3] ne '';
			$lang{$line[2]} = $line[6] if $line[2] ne '';
			$lang{$line[1]} = $line[6] if $line[1] ne '';
			$lang{$line[0]} = $line[6];
		}
	}
	close LANG;
	$lang{'cmn'} = 'Mandarin'; # replace 'Mandarin Chinese'
	$lang{'yue'} = 'Yue Cantonese'; # Cantonese (Hong-Kong), replace 'Yue Chinese'
}
$lang{'-'} = 'none';

# Expansion pattern for countries through ISO-3166 Country Codes (2 letters)
# -- How to get latest ISO-3166 country code data set --
# 1. Browse w/ Firefox to 'https://www.iso.org/obp/ui/#search/code/'
# 2. Select menu: "Tools" --> "Web Developer" --> "Network", developer tools window will open at bottom of page
# 3. On page, top right, select "Results per page": 300
# 4. In developer tools window at bottom, right-click on last "POST" entry (Type "json", about 120 kByte)
# 5. Select "Copy" --> "Copy Response"
# 6. Paste into text file and save as 'iso-3166-1.json' into cgi-bin directory
# 7. Run 'iso-3166-1_parse.pl' in cgi-bin directory to create .tab files
#my $iso3166_table = 'iso-3166-1_full.tab'; # tab-separated values, complete, see instructions above
my $iso3166_table = 'iso-3166-1.tab'; # tab-separated values, abridged (columns #4 and #6), see instructions above
my $iso3166_url = 'https://scarabaeus.org/' . $iso3166_table;
# tab-delimited:
#         ID      char(2)        -- The two-letter 3166 identifier
#         Name    varchar(150)   -- Reference country name 

# Parse file of ISO-3166 codes into hash table
my %country = ();
if (open CTRY, "<${iso3166_table}") {
	my $header = 1;
	while (<CTRY>) {
		chomp;
		my @line = split "\t";
		#print "ID:$line[0], Country:$line[1]<br>\n";
		if ($header) {
			$header = 0; # Skip header line
		} else {
			$country{uc($line[0])} = $line[1] if ($line[0]);
		}
	}
	close CTRY;
}

# Expansion pattern for audio codec. Short tag has to appear at the start of the description, followed by an optional space.
my %audio_codec = (
	'PC', 'PCM', 
	'DD', 'Dolby Digital', 
	'DP', 'Dolby Digital Plus', 
	'AD', 'Dolby Digital Plus / Atmos', 
	'TH', 'Dolby TrueHD', 
	'AT', 'Dolby TrueHD / Atmos', 
	'DT', 'DTS', 
	'HR', 'DTS-HD High Resolution', 
	'MA', 'DTS-HD Master Audio', 
	'DX', 'DTS:X MA', 
	'HX', 'DTS:X HR', 
	'DH', 'DTS Headphone:X', 
	'AU', 'Auro3D', 
	'MH', 'MPEG-H 3D Audio', 
	'AA', 'AAC');

my %video_format = (
	'',  '4:3', 
	's', '4:3', 
	'a', '16:9', 
	'p', '16:9,720p', 
	'i', '16:9,1080i', 
	'h', '16:9,1080p', 
	'w', '21:9,1080p', 
	'u', '16:9,2160p');

my %hdr_format = (
	'',  ',SDR', 
	's', ',SDR', 
	'h', ',HDR10', 
	'd', ',Dolby Vision', 
	'p', ',HDR10+', 
	't', ',Technicolor HDR', 
	'g', ',HLG');

my %media_type = (
	'c', 'CD', 
	's', 'SACD', 
	'b', 'Blu-ray', 
	't', 'Blu-ray 3D', 
	'w', 'Blu-ray MoD', 
	'u', 'UltraHD Blu-ray', 
	'h', 'HD-DVD', 
	'hd', 'HD-DVD/DVD', 
	'l', 'Laser Disc', 
	'm', 'VideoCD', 
	'v', 'VHS', 
	'x', 'CD/DVD-ROM', 
	'a', 'UV/MA', 
	'i', 'iTunes', 
	'y', 'DigCopy', 
	'o', 'DVD Audio', 
	'r', 'DVD-R', 
	'd', 'DVD');

#
# Helper Functions
#

# Expansion function for audio and subtitle track descriptions
sub expand_lang
{
	my ($value) = @_;
	my $lang = ''; # Language(s) of audio or subtitle tracks
	my ($tracks, $track, $language);
	my $count = 0;
	for $tracks (split /\//, $value) { # separate language tracks by '/'
		my $descr = '';
		$descr = $1 if ($tracks=~s/ ?\((.*)\)//); # extract description, appended in '()'
		# expand audio coding type at beginning of description
		foreach $codec (keys %audio_codec) {
			last if $descr=~s/^${codec} ?/$audio_codec{$codec} /i;
		}
		for $track (split /,/, $tracks) { # separate tracks that share an add-on by ','
			my $multilang = ''; # one or more language in the track
			my $separator = '';
			for $language (split /\+/, $track) { # separate multi-language track by '+' into ISO language codes
				my $region = '';
				if ($language eq '-') {
					$language = 'none';
				} else {
					if ($language=~/^(.*)-(.*)$/) { # separate region, if appended to language, with '-' as separator
						$language = $1;
						$region = $2;
					}
					if ($language=~/^[a-zA-Z]{2}[a-zA-Z]?$/) {
						my $iso = lc($language);
						$language = $lang{$iso}; # expand 2 or 3 letter ISO 639 code to language name
						$language = '<font color="red"><b>' . $iso . '</b></font>' unless (defined($language) && ($language ne '')); # mark unknown language tag in bold red
					}
					$language .= "($country{uc($region)})" if ($region); # expand 2 letter ISO 3166 code to country name
				}
				$multilang .= $separator . $language;
				$separator = '+';
			}
			$descr=~s/\s*$//;
			$multilang .= ' (' . $descr . ')' if $descr; # attach description
			$lang .= (($count > 0) ? '<br>' : '') . $multilang; # gather list of tracks
			$count++;
		}
	}
	if (($count > 3) && ! defined($query{'raw'})) { # in excess of 3 tracks, make it a size 3 overflow box
		$lang = '<div style="height:3em;overflow:auto;">' . $lang . '</div>';
	}
	return $lang;
}

# Expand single ch. shortcut to full video format name
sub expand_vf
{
	my ($vf) = @_;
	
	if ($vf=~/(\d+\.\d+)(\w?)(\w?)(\w?)(\w?)\s*(.*)$/) {
		return 
			(
				"$1:1<font size=\"-1\">[" . 
				($video_format{$2} ? $video_format{$2} : $2) . 
				(((($2 eq 'u') || ($3 ne '')) && ($3 ne 's')) ? $hdr_format{'h'} : '') . 
				((($3 ne '') && ($3 ne 'h')) ? ($hdr_format{$3} ? $hdr_format{$3} : $3) : '') . 
				(($4 ne '') ? ($hdr_format{$4} ? $hdr_format{$4} : $4) : '') . 
				(($5 ne '') ? ($hdr_format{$5} ? $hdr_format{$5} : $5) : '') . 
				"]<\/font>" . 
				($6 ? " $6" : '')
			,
				$2=~/[hwu]/ ? 24 : 0
			);
	} else {
		return ($vf, 0);
	}
}

# allow flexible line break at occurrences of '/'
sub break
{
	my ($link) = @_;
	$link=~s/\//\/<wbr>/g if $link=~/[^\s]{10,}/;
	return $link;
}

# Create search and results fields, used multiple times on the page
# Uses global vars $query, %query, $limit
sub search
{
	unless (defined($query{'raw'})) {
		my $q = '&' . $query;
		$q =~ s/\&search=[^&]*//;
		$q =~ s/^\&//;
		print "<table><tr><td><form name=\"Search\">\n";
		my $search = '';
		$search = $query{'search'} if defined ($query{'search'});
		print "\t<a name=\"Search\">Search</a>: <input name=\"search\" type=\"text\" size=\"20\" value=\"${search}\" >\n";
		for my $key (keys %query) {
			print "\t<input name=\"$key\" type=\"hidden\" value=\"$query{$key}\" >\n" unless ($key=~/^search$/);
		}
		print "<input type=\"reset\" value=\"X\">\n";
		print "</form></td>\n";
		
		print "<td width=\"100\"></td>";
		
		print "<td><form name=\"Limit\">\n";
		for my $key (keys %query) {
			print "\t<input name=\"$key\" type=\"hidden\" value=\"$query{$key}\" >\n" unless ($key=~/^limit$/);
		}
		print "<label for=\"limit\">Show</label>\n";
		print "<select id=\"limit\" name=\"limit\" size=\"1\" onchange=\"this.form.submit()\">\n";
		print "<option value=\"0\"" . (($limit == 0) ? " selected" : "") . ">all</option>\n";
		print "<option value=\"50\"" . (($limit == 50) ? " selected" : "") . ">up to 50</option>\n";
		print "<option value=\"100\"" . (($limit == 100) ? " selected" : "") . ">up to 100</option>\n";
		print "<option value=\"200\"" . (($limit == 200) ? " selected" : "") . ">up to 200</option>\n";
		print "<option value=\"500\"" . (($limit == 500) ? " selected" : "") . ">up to 500</option>\n";
		print "</select> results\n";
		print "</form></td>\n";
		
		print "<td width=\"100\"></td>";
		
		print "<td><form name=\"Filter\">\n";
		for my $key (keys %query) {
			print "\t<input name=\"$key\" type=\"hidden\" value=\"$query{$key}\" >\n" unless ($key=~/^filter$/);
		}
		print "<label for=\"filter\">Show only</label>\n";
		print "<select id=\"filter\" name=\"filter\" size=\"1\" onchange=\"this.form.submit()\">\n";
		print "<option value=\"$filter\" selected>";
		if ($filter ne '') {
			my $cma = 0;
			for my $f (split(//, $filter)) {
				print ', ' if ($cma);
				$cma = 1;
				print "$media_type{$f}";
			}
		} else {
			print "(select below)";
		}
		print "</option>\n";
		my (@opt, @clear);
		for my $key (keys %media_type) {
			if ($filter=~/${key}/i) {
				my $f = $filter;
				$f=~s/${key}//ig;
				my @c = ("\t<option value=\"$f\">Clear: ", "$media_type{$key}</option>");
				push(@clear, \@c);
			} else {
				my @o = ("\t<option value=\"$filter$key\">" . (($filter ne '') ? "and " : ''), "$media_type{$key}</option>\n");
				push(@opt, \@o);
			}
		}
		foreach my $option (sort { lc($a->[1]) cmp lc($b->[1]) } @opt) {
			print join("", @{$option}) . "\n";
		}
		foreach my $option (sort { lc($a->[1]) cmp lc($b->[1]) } @clear) {
			print join("", @{$option}) . "\n";
		}
		print "<option value=\"\"><i>Clear (Show All Types)</i></option>\n" if ($filter ne '');
		print "</select>\n";
		print "</form></td></tr></table>\n";
	}
}



#
# Start of HTML
#

# get CGI parameter
%query = ();
$query{'file'} = 'dvd_list.txt';
$query = $ENV{'QUERY_STRING'};
chomp $query;
for (split (/&/, $query)) {
	if (/=/) {
		my ($param,$content) = split(/=/, $_, 2);
		$content =~ tr/+/ /;
		$content =~ s/%([0-9a-fA-F]{2})/pack('c',hex($1))/ge;
		$query{$param} = $content;
	} elsif ($_) {
		$query{'file'} = $_;
	}
}

$limit = defined($query{'limit'}) ? $query{'limit'} : 0;
$filter = defined($query{'filter'}) ? $query{'filter'} : '';

$dbfile = $query{'file'};
$dbfile=~s/^.*\///; # Delete everything up to and including the last slash
$dbfile=~s/\/.*$//; 
$dbfile = $dbpath . $dbfile;

$title .= " Reverse" if (defined($query{'revsort'}));
$title .= " Sorted by Col. $query{'sort'}" if (defined($query{'sort'}));

# print HTTP header
print "MIME-Version: 1.0\nContent-Type: text/html\n\n";

my $title = 'Media List';



# print HTML header

# background colors:
#               	light   dark
# dark line			#F0F0F0 #505050 (grey)
# title line	   	#B0B0D0 #606080 (blue-grey)
# section summary 	#E0FFE0 #809F80 (lime green)
# total summary   	#D0E0D0 #709070 (green-grey)

#	<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
print <<END;
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "https://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
	<meta http-equiv="content-type" content="text/html; charset=utf-8">
	<link rel="stylesheet" type="text/css" href="/scarabaeus.css">
	<title>${title}</title>
	<style>
		.alt_row {background: #F0F0F0; }
		.title_row {background: #B0B0D0; }
		.summary_row {background: #E0FFE0; }
		.total_row {background: #D0E0D0; }
		\@media (prefers-color-scheme: dark) {
			.alt_row {background: #505050; }
			.title_row {background: #606080; }
			.summary_row {background: #809F80; }
			.total_row {background: #709070; }
		}
	</style>
</head>
<body>
END

#foreach $ctr (sort keys %country) {
#	print "'${ctr}': '$country{$ctr}'<br>\n";
#}

search();

my (
	$items, $total_items, 
	$items_dvd, $total_items_dvd, 
	$items_cd, $total_items_cd, 
	$items_bd, $total_items_bd, 
	$items_3d, $total_items_3d, 
	$items_uhd, $total_items_uhd, 
	$items_hd, $total_items_hd, 
	$items_ld, $total_items_ld, 
	$items_vcd, $total_items_vcd, 
	$items_vhs, $total_items_vhs, 
	$items_dig, $total_items_dig, 
	$discs, $total_discs, 
	$discs_dvd, $total_discs_dvd, 
	$discs_cd, $total_discs_cd, 
	$discs_bd, $total_discs_bd, 
	$discs_3d, $total_discs_3d, 
	$discs_uhd, $total_discs_uhd, 
	$discs_hd, $total_discs_hd, 
	$discs_ld, $total_discs_ld, 
	$discs_vcd, $total_discs_vcd, 
	$discs_vhs, $total_discs_vhs, 
	$discs_dig, $total_discs_dig, 
	$times, $total_times
) = (0, 0, 0, 0, 0,  0, 0, 0, 0, 0,   0, 0, 0, 0, 0,  0, 0, 0, 0, 0,   0, 0, 0, 0, 0,  0, 0, 0, 0, 0,   0, 0, 0, 0, 0,  0, 0, 0, 0, 0,   0, 0, 0, 0, 0,  0);
my ($dollars, $total_dollars) = (0.00, 0.00);

my @lines = ();
my @sections = ();
my ($cur_line, $cur_section, $sections) = (0, 0, 0);
my $selected_section = 1;
my $td_tag = 'th';
my $td = $td_tag . ' valign="baseline" align="left" class="alt_row"';
my $firstheader = 1;
my $firstseg = 1;
open LIST, "<${dbfile}" or die "list file '${dbfile}' not found!";
print "<table border=0 cellspacing=0 cellpadding=2 style=\"table-layout: fixed; width: 100%\"><thead>\n";
while (<LIST>) {
	chomp;
	s/^\#\#\#\s?/[XXX] / if $query{'xxx'};
	s/^\#.*$//;
	next if /^\s*$/;
	next if ($td!~/^th/) && $query{'search'} && !/$query{'search'}/i && !/^--/;
	my @line = split "\t";
	if ($#line < 7) {  # Convert legacy file format
		for (my $i = 12; $i > 0; $i--) {
			$line[$i] = ($i < 4) ? '' : ($line[$i - 3] ? $line[$i - 3] : '') ;
		}
	}
	while ($#line < 12) {
		push @line, '';
	}
	next if ((! $query{'all'}) && defined($line[5]) && ($line[5] =~ /-/));
	my $line = "\t<tr>";
	my $dollar = 0.00;
	my @key = ();
	my $imdbsearch = $line[0];
	$imdbsearch =~ s/ - .*$//;
	$imdbsearch =~ s/^\[XXX\] //;
	$imdbsearch =~ s/^P: //;
	$imdbsearch = decode_entities($imdbsearch);
	#$imdbsearch =~ s/\&(\d+);/chr($1)/eg;
	$imdbsearch = uri_escape($imdbsearch, "^A-Za-z0-9\-\._~");
	#$imdbsearch =~ s/([^A-Za-z0-9\-\._~ ])/sprintf("%%%02X",ord($1))/eg;
	#$imdbsearch =~ s/ /+/g;
	my $fps = 0;
	if (defined($query{'sort'})) {
		@key = @line;
		$key[0] =~ s/^\[XXX\] //;
		$key[0] =~ s/^P: //;
		# Remove article from title
		$key[0] =~ s/^\s*a\s+//i;
		$key[0] =~ s/^\s*an\s+//i;
		$key[0] =~ s/^\s*the\s+//i;
		$key[0] =~ s/^\s*der\s+//i;
		$key[0] =~ s/^\s*die\s+//i;
		$key[0] =~ s/^\s*das\s+//i;
		# Expand numbers in Title and Release to 8 digits
		$key[0] =~ s/(\d+)/sprintf "%08d", $1/eg;
		$key[1] =~ s/(\d+)/sprintf "%08d", $1/eg if defined($key[1]);
		# Remove anything besides letters, digits and white space from title and release
		$key[0] =~ s/[^\w\d\s]//;
		$key[1] =~ s/[^\w\d\s]// if defined($key[1]);
		# Simplify URLs
		$key[8] =~ s/(https?\:\/\/)(www\.)?// if defined($key[8]);
		$key[12] =~ s/(https?\:\/\/)(www\.)?// if defined($key[12]);
	}
	unless (/^--/) { # Regular entry
		next unless ($selected_section);
		push @line, '' while ($#line < 7);
		$line[0] =~s/^\*{3}\s+(.*)$/<b>$1<\/b>/;
		if ($td!~/^th/) {
			$line[0] = "<b>$line[0]</b>";
			$line[0] =~ s/^<b>(\[XXX\] )?P: /<i>Preorder:<\/i> <b>$1/g;
			if ($line[3]) {
				# Aspect Ratio (x.xx --> x.xx:1) and video format
				my @video_format = ();
				for $vf (split /\//, $line[3]) {
					my ($format, $rate) = expand_vf($vf);
					push @video_format, $format;
					$fps = 24 if ($rate == 24); # cumulative, any 24 fps or none
				}
				$line[3] = join('<br>', @video_format);
			}
			if (defined($line[5])) {
				$line[5] =~ s/^p/P<font size=\"-1\">AL<\/font>/i;
				$line[5] =~ s/^n/N<font size=\"-1\">TSC<\/font>/i;
				$line[5] =~ s/([\d\-A-F\?]+\??)$/\/Reg.$1/;
			}
			#$line[6] = break($line[6]);
			#$line[7] = break($line[7]);
			$line[6] = expand_lang($line[6]) if defined($line[6]);
			$line[7] = expand_lang($line[7]) if defined($line[7]);
		}
	} else {  # start of new segment == end of previous segment, print summary
		my $section = $line[0];
		$section =~s/^--\s*(.*)\s*/$1/; # section tag
		push @sections, $section;
		$sections++;
		my $tag = $section;
		$tag =~ s/[^a-zA-Z0-9_]//g;
		if (defined($query{'section'})) {
			$selected_section = ($query{'section'} eq $tag);
			next unless ($selected_section);
		}
		$line[0] = "<b>-- <a name=\"${tag}\">" . $section . "</a></b>";
		$td_tag = 'td';
		if (defined($query{'raw'})) {
			$td = $td_tag . (($td eq $td_tag) ? " class=\"alt_row\"" : '');
		} else {
			$td = $td_tag . " class=\"summary_row\"";
		}
		unless (defined($query{'sort'}) || defined($query{'raw'}) || defined($query{'section'}) || $firstseg) {
			$line .= " <${td} colspan=3>${items} (${items_dvd} DVD, ${items_cd} CD, ${items_bd} Blu-ray (${items_3d} 3D), ${items_uhd} UltraHD Blu-ray, ${items_hd} HD-DVD, ${items_ld} Laser Disc, ${items_vcd} VideoCD, ${items_vhs} VHS, ${items_dig} DigCopy)</${td_tag}> ";
			if ($times) {
				$times = sprintf("%d day%s, %d:%02d&nbsp;(%d&nbsp;min.)", $times / 1440, (int($times / 1440) == 1) ? '' : 's', ($times / 60) % 24, $times % 60, $times);
				$line .= "<${td}>${times}</${td_tag}> ";
			} else {
				$line .= "<${td}></td> ";
			}
			$dollars = sprintf("%0.2f", $dollars);
			$line .= "<${td} colspan=4>${discs} (${discs_dvd} DVD, ${discs_cd} CD, ${discs_bd} Blu-ray (${discs_3d} 3D), ${discs_uhd} UltraHD Blu-ray, ${discs_hd} HD-DVD, ${discs_ld} Laser Disc, ${discs_vcd} VideoCD, ${discs_vhs} VHS, ${discs_dig} DigCopy, )</${td_tag}> <${td} colspan=1></td> " . (defined($query{'cost'}) ? "<${td}>\$${dollars}</td> " : '') . "<${td} colspan=1></td> </tr>\n";
			$line .= "\t<tr> <td colspan=9></td> </tr>\n\n\t<tr>";
		}
		# Add segment counts to totals
		$total_discs_dvd += $discs_dvd;
		$total_discs_cd += $discs_cd;
		$total_discs_bd += $discs_bd;
		$total_discs_3d += $discs_3d;
		$total_discs_uhd += $discs_uhd;
		$total_discs_hd += $discs_hd;
		$total_discs_ld += $discs_ld;
		$total_discs_vcd += $discs_vcd;
		$total_discs_vhs += $discs_vhs;
		$total_discs_dig += $discs_dig;
		# Reset counts for next segment
		$items = 0;
		$items_dvd = 0;
		$items_cd = 0;
		$items_bd = 0;
		$items_3d = 0;
		$items_uhd = 0;
		$items_hd = 0;
		$items_ld = 0;
		$items_vcd = 0;
		$items_vhs = 0;
		$items_dig = 0;
		$discs = 0;
		$discs_dvd = 0;
		$discs_cd = 0;
		$discs_bd = 0;
		$discs_3d = 0;
		$discs_uhd = 0;
		$discs_hd = 0;
		$discs_ld = 0;
		$discs_vcd = 0;
		$discs_vhs = 0;
		$discs_dig = 0;
		$times = 0;
		$dollars = 0.00;
		$firstseg = 0;
		next if defined($query{'raw'});
	}
	if ($td=~/^th/) {
		my $q = '&' . $ENV{'QUERY_STRING'};
#		next if defined($query{'raw'});
		$q =~ s/\&revsort=\d+//;
		$q =~ s/\&sort=\d+//;
		$q =~ s/^\&//;
		for (my $i = 0; $i < 12; $i ++) {
			if (defined($query{'sort'}) && ($query{'sort'} == $i)) {
				if (defined($query{'revsort'})) {
					$line[$i] = "<u><a href=\"$ENV{SCRIPT_NAME}" . ($q ? '?' : '') . "${q}\">$line[$i]</a></u>";
				} else {
					$line[$i] = "<a href=\"$ENV{SCRIPT_NAME}?${q}" . ($q ? '&' : '') . "sort=${i}\&revsort=1\">$line[$i]</a>";
				}
			} else {
				$line[$i] = "<a href=\"$ENV{SCRIPT_NAME}?${q}" . ($q ? '&' : '') . "sort=${i}\">$line[$i]</a>";
			}
		}
		$line[6] .= "<br><sup><a href=\"${iso639_url}\" target=\"_blank\">[language list]</a></sup>";
		$line[7] .= "<br><sup><a href=\"${iso3166_url}\" target=\"_blank\">[country list]</a></sup>";
	} else {
		
		# Media type (Disc type, digital format)
		$key[4] = 0;
		if (defined($line[4]) && ($line[4] ne '')) {
			my $first = 1;
			my $first_3d = 1;
			my $first_dig = 1;
			my $got_bd = 0;
			my $skip = 0;
			$skip = 1 if ($filter ne '');
			if ($line[4] =~ /\D/) {  # number(s) with qualifier(s)
				my $num = $line[4];
				my ($t, $n);
				$line[4] = '';
				while ($num =~ /(\d+)([^0123456789(]*)(\([^()]+?\))?(.*)$/) { # 1+ digits, followed by 0+ non-digits/open paren, followed by a portion in parens (if any), followed by 0+ any. 3rd match group will start with digit, if any.
					$line[4] .= ", " if ($line[4]);
					($n, $t, $s, $num) = ($1, $2, $3, $4);
					if ($t =~ /^c/i) {
						$skip = 0 if ($filter =~ /c/i);
						$t = $media_type{'c'};
						$discs_cd += $n;
						$items_cd ++ if $first;
						$total_items_cd ++ if $first;
					} elsif ($t =~ /^s/i) {
						$skip = 0 if ($filter =~ /s/i);
						$t = $media_type{'s'};
						$discs_cd += $n;
						$items_cd ++ if $first;
						$total_items_cd ++ if $first;
					} elsif ($t =~ /^b/i) {
						$skip = 0 if ($filter =~ /b/i);
						$t = $media_type{'b'};
						$discs_bd += $n;
						$items_bd ++ if $first;
						$total_items_bd ++ if $first;
						$got_bd = 1;
					} elsif ($t =~ /^t/i) { # Count Blu-Ray 3D items as both Blu-Ray and Blu-Ray 3D
						$skip = 0 if ($filter =~ /t/i);
						$t = $media_type{'t'};
						$discs_3d += $n;
						$items_3d ++ if ($first || $first_3d);
						$total_items_3d ++ if ($first || $first_3d);
						$items_bd ++ if ($first || ($first_3d && (! $got_bd)));
						$total_items_bd ++ if ($first || ($first_3d && (! $got_bd)));
						$first_3d = 0;
					} elsif ($t =~ /^w/i) {
						$skip = 0 if ($filter =~ /w/i);
						$t = $media_type{'w'};
						$discs_bd += $n;
						$items_bd ++ if $first;
						$total_items_bd ++ if $first;
						$got_bd = 1;
					} elsif ($t =~ /^u/i) {
						$skip = 0 if ($filter =~ /u/i);
						$t = $media_type{'u'};
						$discs_uhd += $n;
						$items_uhd ++ if $first;
						$total_items_uhd ++ if $first;
					} elsif ($t =~ /^hd/i) {
						$skip = 0 if ($filter =~ /hd/i);
						$t = $media_type{'hd'};
						$discs_hd += $n;
						$items_hd ++ if $first;
						$total_items_hd ++ if $first;
					} elsif ($t =~ /^h/i) {
						$skip = 0 if ($filter =~ /h/i);
						$t = $media_type{'h'};
						$discs_hd += $n;
						$items_hd ++ if $first;
						$total_items_hd ++ if $first;
					} elsif ($t =~ /^l/i) {
						$skip = 0 if ($filter =~ /l/i);
						$t = $media_type{'l'};
						$discs_ld += $n;
						$items_ld ++ if $first;
						$total_items_ld ++ if $first;
					} elsif ($t =~ /^m/i) {
						$skip = 0 if ($filter =~ /m/i);
						$t = $media_type{'m'};
						$discs_vcd += $n;
						$items_vcd ++ if $first;
						$total_items_vcd ++ if $first;
					} elsif ($t =~ /^v/i) {
						$skip = 0 if ($filter =~ /v/i);
						$t = $media_type{'v'};
						$discs_vhs += $n;
						$items_vhs ++ if $first;
						$total_items_vhs ++ if $first;
					} elsif ($t =~ /^x/i) {
						$skip = 0 if ($filter =~ /x/i);
						$t = $media_type{'x'};
					} elsif ($t =~ /^a/i) {
						$skip = 0 if ($filter =~ /a/i);
						$t = $media_type{'a'};
						$discs_dig += $n;
						$items_dig ++ if $first_dig;
						$total_items_dig ++ if $first_dig;
						$first_dig = 0;
					} elsif ($t =~ /^i/i) {
						$skip = 0 if ($filter =~ /i/i);
						$t = $media_type{'i'};
						$discs_dig += $n;
						$items_dig ++ if $first_dig;
						$total_items_dig ++ if $first_dig;
						$first_dig = 0;
					} elsif ($t =~ /^y/i) {
						$skip = 0 if ($filter =~ /y/i);
						$t = $media_type{'y'};
						$discs_dig += $n;
						$items_dig ++ if $first_dig;
						$total_items_dig ++ if $first_dig;
						$first_dig = 0;
					} elsif ($t =~ /^o/i) {
						$skip = 0 if ($filter =~ /o/i);
						$t = $media_type{'o'};
						$discs_dvd += $n;
						$items_dvd ++ if $first;
						$total_items_dvd ++ if $first;
					} elsif ($t =~ /^r/i) {
						$skip = 0 if ($filter =~ /r/i);
						$t = $media_type{'r'};
						$discs_dvd += $n;
						$items_dvd ++ if $first;
						$total_items_dvd ++ if $first;
					} else { # 'd' or unknown designator, default to DVD
						$skip = 0 if ($filter =~ /d/i);
						$t = $media_type{'d'};
						$discs_dvd += $n;
						$items_dvd ++ if $first;
						$total_items_dvd ++ if $first;
					}
					$key[4] += $n;
					$discs += $n;
					$total_discs += $n;
					$line[4] .= '<br>' if ($line[4]);
					$line[4] .= $n . '&nbsp;' . $t . (($n == 1) ? '' : 's');
					$line[4] .= '&nbsp;' . $s if ($s);
					$first = 0;
				}
				next if $skip;
			} else {  # just a number, assume DVD
				$skip = 0 if ($filter =~ /d/i);
				next if $skip;
				my $dvd_count = 1;
				$dvd_count = $line[4] if defined($line[4]) && ($line[4] > 0);
				$key[4] += $dvd_count;
				$discs += $dvd_count;
				$discs_dvd += $dvd_count;
				$items_dvd ++;
				$total_discs += $dvd_count;
				$total_items_dvd ++;
				$line[4] .= '&nbsp;DVD' . (($dvd_count == 1) ? '' : 's');
			}
			$items ++;
			$total_items ++;
		} else { # No info, assume DVD without specific number of discs
			next if (($filter ne '') && ($filter =~ /d/i));
			next if (defined($query{'raw'}));
		}
		
		# Running time (PAL 2:2 pulldown adjustment)
		if ($line[2] =~/\d/) {
			my $time_line = '';
			$key[2] = 0;
			for $rt (split /\//, $line[2]) {
				my $runtime = defined($rt) ? $rt : 0;
				if ($runtime =~/(\d+)\:(\d+)/) {
					$runtime = $1 * 60 + $2;
				} elsif ($runtime =~/\D/) {
					$runtime = 0;
				}
				my $time_entry = sprintf("%d:%02d&nbsp;", $runtime / 60, $runtime % 60);
				if (($line[5]=~/P/) && ($fps != 24)) {
					$time_entry .= sprintf("(%d[%d]&nbsp;min.)", $runtime, $runtime * 25 / 24);
				} else {
					$time_entry .= sprintf("(%d&nbsp;min.)", $runtime);
				}
				$time_line .= '<br>' if ($time_line);
				$time_line .= $time_entry;
				$key[2] = $runtime if (defined($query{'sort'}) && ! $key[2]);
				$times += $runtime;
				$total_times += $runtime;
			}
			$line[2] = $time_line;
		}
		
		# Cost (currency conversion, 2018)
		if (defined($query{'cost'}) && defined($line[10])) {
			my ($currency, $amount) = ('D', 0);
			if ($line[10] =~ /^([A-Z\$€£]?)(.*)$/) {
				$currency = $1 if defined($1);
				$amount = $2 if defined($2);
				$amount = 0 unless ($amount =~ /\d+\.\d{2}/);
			}
			if ($currency =~ /^[E€]/) {
				$dollar = ($amount * 1.1);
				$line[10] =~ s/^E/&euro;/;
				$line[10] .= sprintf(" (~\$%0.2f)", $dollar);
			} elsif ($currency =~ /^[P£]/) {
				$dollar = ($amount * 1.3);
				$line[10] =~ s/^P/&pound;/;
				$line[10] .= sprintf(" (~\$%0.2f)", $dollar);
			} elsif ($amount) {
				$dollar = $amount;
				$line[10] = sprintf("\$%0.2f", $dollar);
			}
			$key[10] = $dollar;
			$dollars += $dollar;
			$total_dollars += $dollar;
		} else {
			$key[10] = 0;
		}
		
		# ASIN
		if (defined($line[11]) && ($line[11] ne "") && (! defined($query{'raw'}))) {
			my ($country, $asin) = ('us', $line[11]);
			if ($asin =~ /\//) { # Optional: prefix with Amazon country (e.g. "de/" for amazon.de)
				($country, $asin) = split('/', $asin);
			}
			if ($asin=~/^B0/ || $asin=~/^63/ || $asin=~/^07/ || $asin=~/^18/ || $asin=~/^38/ || $asin=~/^39/) {
				if ($country eq 'de') { # Germany
					$line[11] = "<a href=\"http://amazon.de/dp/${asin}/\" target=\"_blank\">${asin}</a>";
				} elsif ($country eq 'jp') { # Japan
					$line[11] = "<a href=\"http://amazon.co.jp/dp/${asin}/\" target=\"_blank\">${asin}</a>";
				} elsif ($country eq 'uk') { # UK
					$line[11] = "<a href=\"http://amazon.co.uk/dp/${asin}/\" target=\"_blank\">${asin}</a>";
				} elsif ($country eq 'fr') { # France
					$line[11] = "<a href=\"http://amazon.fr/dp/${asin}/\" target=\"_blank\">${asin}</a>";
				} elsif ($country eq 'it') { # Italy
					$line[11] = "<a href=\"http://amazon.it/dp/${asin}/\" target=\"_blank\">${asin}</a>";
				} elsif ($country eq 'ca') { # Canada
					$line[11] = "<a href=\"http://amazon.ca/dp/${asin}/\" target=\"_blank\">${asin}</a>";
				} elsif ($country eq 'us') { # USA
					#$line[11] = "<a href=\"http://amazon.com/dp/${asin}/\" target=\"_blank\">${asin}</a>";
					$line[11] = "<a href=\"http://amzn.com/dp/${asin}/\" target=\"_blank\">${asin}</a><sup><a href=\"https://catalog-retail.amazon.com/abis/syh/DisplayCondition/?asin=${asin}\" target=\"_blank\">*</a></sup>";
					# http://www.amazon.com/gp/offer-listing/${asin}/?condition=new
					# http://www.amazon.com/gp/offer-listing/${asin}/?condition=used
					# https://catalog-retail.amazon.com/abis/syh/DisplayCondition/?asin=${asin}
				} else {
					$line[11] = "<a href=\"http://amazon.${country}/dp/${asin}/\" target=\"_blank\">${asin}</a>";
				}
			}
		}
		
	}
	if ($line[0]=~/^<b>--/) {
		next if defined($query{'raw'});
		$td_tag = 'td';
		$td = $td_tag . " class=\"title_row\"";
		$line .= " <${td}>$line[0]</${td_tag}> =====";
	} else {
		if ($td!~/^th/) {
			# # Forced line breaks in Title and Release
			# $line[0] =~ s/( [-\/] )/$1\<br\>&nbsp;&nbsp;/g;
			# #$line[0] =~ s/: /:\<br\>&nbsp;&nbsp;/g;
			# $line[0] =~ s/  / \<br\>&nbsp;&nbsp;/g;
			# $line[1] =~ s/( [-\/] )/$1\<br\>&nbsp;&nbsp;/g;
			# #$line[1] =~ s/: /:\<br\>&nbsp;&nbsp;/g;
			#$line[0] = break($line[0]);
			#$line[1] = break($line[1]);
			
			# Expand IMDB title, character, or company tags ('tt', 'ch', or 'co', followed by 7 digits)
			$line[8] =~s/^(tt\d{7,8})$/https:\/\/www.imdb.com\/title\/${1}\//;
			$line[8] =~s/^(ch\d{7,8})$/https:\/\/www.imdb.com\/character\/${1}\//;
			$line[8] =~s/^(co\d{7,8})$/https:\/\/www.imdb.com\/company\/${1}\//;
			
			# Add link to Title
			if ((! defined($query{'raw'})) && defined($line[8]) && ($line[8] =~ /(https?\:\/\/)(www\.)?(\S*?)(\/?)(\s*)$/)) {
				my $url = '';
				my $link = '';
				my $spacing = '';
				$url = $1 if defined($1);
				$url .= $2 if defined($2);
				$url .= $3 if defined($3);
				$url .= $4 if defined($4);
				$link .= $3 if defined($3);
				$spacing = $5 if defined($5);
				$line[8] =~ s/https?\:\/\/(www\.)?\S*?\/?\s*$/"<a href=\"${url}\" target=\"_blank\">" . break(${link}) . "<\/a>${spacing}"/ge;
				$line[0] = "<a href=\"${url}\" target=\"_blank\">" . $line[0]. "<\/a>";
			} elsif (! defined($query{'raw'})) {
				# Create link to IMDB search for title
				$line[0] = "<a href=\"https://imdb.com/find?q=${imdbsearch}&s=all\" target=\"_blank\">" . $line[0]. "<\/a>";
			}
			
			# Add link to Release
			if (! defined($query{'raw'})) {
				if (defined($line[12]) && ($line[12] =~ s/(https?\:\/\/)(www\.)?(\S*?)(\/?)(\s*)$/"<a href=\"$1$2$3$4\" target=\"_blank\">" . break($3) . "<\/a>$5"/ge)) {
					$line[1] = "<a href=\"$1$2$3$4\" target=\"_blank\">" . $line[1]. "<\/a>";
				} elsif ($line[1]=~/criterion\s(\d+)/) {
					$line[1] = "<a href=\"https://www.criterion.com/search\#stq=Spine+\%23${1}\" target=\"_blank\">" . $line[1]. "<\/a>";
				} elsif ($line[1]=~/Vinegar Syndrome\s([A-Z0-9\-]+ ?[A-Z]?[A-Z]?[A-Z]?[A-Z]?)(\s-)?/) {
					my $sku = $1;
					$sku=~s/ /+/g;
					$line[1] = "<a href=\"https://vinegarsyndrome.com/search\?q=${sku}\&type=product\" target=\"_blank\">" . $line[1]. "<\/a>";
				} else {
					$line[1] = "<a href=\"https://www.blu-ray.com/search/?quicksearch=1&quicksearch_country=US&quicksearch_keyword=${imdbsearch}&section=theatrical\" target=\"_blank\">" . $line[1]. "<\/a>";
				}
			}
		}
		
		#my $w = ' width="25%"';
		#my $w = ' nowrap="nowrap"';
		#my $w = ' style="width:25%"';
		my $w = ' style="word-wrap: break-word; flex-wrap: wrap;"';
		#my $w = ' style="flex-wrap: wrap;"';
		#my $w = '';
		my $ww = ' width="20%"';
		for (my $i = 0; $i < 12; $i ++) {
			if ($i == 1) {
				$ww = ' width="12%"';
			} elsif ($i == 2) {
				#$w = ' style="word-wrap: break-word; flex-wrap: wrap;"';
				#$w = ' nowrap="nowrap"';
				#$w = '';
				$ww = ''; # "auto" for remainder of columns
			}
			if (($i != 10) || defined($query{'cost'})) {
				$line .= defined($line[$i]) ? " <${td}${w}${ww}>$line[$i]</${td_tag}>" : " <${td}></${td_tag}>" if ($query{'url'} || ($i != 8));
				#$w = ' nowrap="nowrap"';
				#$w = '';
			}
		}
	}
	$line .= " </tr>\n";
	if ($firstheader) {
		$line .= "</thead>\n<tbody>\n";
		$firstheader = 0;
	}
	if (($td!~/^th/) && defined($query{'sort'})) {
		unless (/^--/ || /^\*\*\*/) {
			my $sort_key = '';
			$sort_key = $key[$query{'sort'}] if defined($key[$query{'sort'}]);
			push @lines, $key[0] . "\t" . $sort_key . $line; # Always sort by Title in second order
		}
	} else {
		push @lines, $line;
	}
	$td_tag = 'td';
	$td = $td_tag . (($td eq $td_tag) ? " class=\"alt_row\"" : '');
}
close LIST;

sub bykeys {
	my ($a0, $a1, $b0, $b1) = ('', '', '', '');
	($a0, $a1) = ($1, $2) if ($a =~ /^(.*?)\t(.*?)\t/);
	($b0, $b1) = ($1, $2) if ($b =~ /^(.*?)\t(.*?)\t/);
	(lc($a1) cmp lc($b1)) || (lc($a0) cmp lc($b0));
}

sub bykeysnum {
	my ($a0, $a1, $b0, $b1) = ('', '', '', '');
	($a0, $a1) = ($1, $2) if ($a =~ /^(.*?)\t(.*?)\t/);
	($b0, $b1) = ($1, $2) if ($b =~ /^(.*?)\t(.*?)\t/);
	$a1 = 0 unless $a1;
	$b1 = 0 unless $b1;
	($a1 <=> $b1) || (lc($a0) cmp lc($b0));
}

if (defined($query{'sort'})) { # Print sorted results
	my @sorted;
	# By Duration, Disc count, or Price: Numerical sort, otherwise alphabetical
	if (($query{'sort'} == 3) || ($query{'sort'} == 4) || ($query{'sort'} == 10)) {
		if (defined($query{'revsort'})) {
			@sorted = reverse sort bykeysnum @lines;
		} else {
			@sorted = sort bykeysnum @lines;
		}
	} else {
		if (defined($query{'revsort'})) {
			@sorted = reverse sort bykeys @lines;
		} else {
			@sorted = sort bykeys @lines;
		}
	}
	if (defined($query{'revsort'})) {
		my $header = pop @sorted;
		unshift @sorted, $header;
	}
	$td_tag = 'td';
	$td = $td_tag;
	for (@sorted) {
		s/^.*?\t.*?\t/\t/;
		$td = $td_tag . (($td eq $td_tag) ? " class=\"alt_row\"" : '');
		s/td class=\"alt_row\"/td/g;
		s/<td/<${td}/g;
		print;
		$cur_line++;
		if ($limit && ($cur_line > $limit)) {
			my $remaining = scalar(@lines) - $cur_line;
			print "\t<tr> <td colspan=" . (defined($query{'cost'}) ? '11' : '10') . " class=\"title_row\">... skipping ${remaining} entries</td> </tr>\n";
			last;
		}
	}
} elsif (defined($query{'raw'})) { # Print raw dataset
	for (@lines) {
		print;
	}
} else { # Print in original order from file, in sections
	my $jump = "<td colspan=" . (defined($query{'cost'}) ? '10' : '9') . " class=\"title_row\">[<font size=\"-1\">";
	if ($limit) {
		my $q = '&' . $ENV{'QUERY_STRING'};
		$q =~ s/\&section=.+\&/\&/;
		$q =~ s/\&section=.*$//;
		$q =~ s/^\&/?/;
		$jump .= "<a href=\"$ENV{SCRIPT_NAME}${q}\">All</a>]";
		for (@sections) {
			my $tag = $_;
			$tag =~ s/[^a-zA-Z0-9_]//g;
			$jump .= " -- <a href=\"$ENV{SCRIPT_NAME}${q}&section=${tag}\">$_</a>";
		}
	} else {
		$jump .= "<a href=\"#\">Top</a>]";
		for (@sections) {
			my $tag = $_;
			$tag =~ s/[^a-zA-Z0-9_]//g;
			$jump .= " -- <a href=\"#${tag}\">$_</a>";
		}
	}
	$jump .= " -- [<script type=\"text/javascript\">document.write(\"<a href=\\\"javascript:document.forms.Search.elements.search.focus();\\\">\");</script><noscript><a href=\"#Search\"></noscript>Search</a></font>]";
	$jump .= " -- [<script type=\"text/javascript\">document.write(\"<a href=\\\"javascript:document.forms.new_entry.elements.new_title.focus();\\\">\");</script><noscript><a href=\"#NewEntry\"></noscript>New Entry</a></font>]";
	$jump .= "</td>";
	for (@lines) {
		if (s/=====/$jump/) {
			$cur_section++;
		} else {
			$cur_line++;
		}
		print;
		if ($limit && ($cur_line > $limit)) {
			my $remaining = scalar(@lines) - $cur_line - $sections + $cur_section;
			print "\t<tr> <td colspan=" . (defined($query{'cost'}) ? '11' : '10') . " class=\"title_row\">... skipping ${remaining} entries</td> </tr>\n";
			last;
		}
	}
	# print last segment summary
	unless (defined($query{'section'})) {
		$td_tag = 'td';
		$td = $td_tag . " class=\"summary_row\"";
		print "\t<tr> <${td} colspan=3>${items} (${items_dvd} DVD, ${items_cd} CD, ${items_bd} Blu-ray (${items_3d} 3D), ${items_uhd} UltraHD Blu-ray, ${items_hd} HD-DVD, ${items_ld} Laser Disc, ${items_vcd} VideoCD, ${items_vhs} VHS, ${items_dig} DigCopy)</${td_tag}> ";
		if ($times) {
			$times = sprintf("%d day%s, %d:%02d&nbsp;(%d&nbsp;min.)", $times / 1440, (int($times / 1440) == 1) ? '' : 's', ($times / 60) % 24, $times % 60, $times);
			print "<${td}>${times}</${td_tag}> ";
		} else {
			print "<${td}></td> ";
		}
		$dollars = sprintf("%0.2f", $dollars);
		print "<${td} colspan=4>${discs} (${discs_dvd} DVD, ${discs_cd} CD, ${discs_bd} Blu-ray (${discs_3d} 3D), ${discs_uhd} UltraHD Blu-ray, ${discs_hd} HD-DVD, ${discs_ld} Laser Disc, ${discs_vcd} VideoCD, ${discs_vhs} VHS, ${discs_dig} DigCopy)</${td_tag}> <${td} colspan=1></td> " . (defined($query{'cost'}) ? "<${td}>\$${dollars}</td> " : '') . "<${td} colspan=1></td> </tr>\n\t<tr>";
	}
}

# Add last segment counts to totals
$total_discs_dvd += $discs_dvd;
$total_discs_cd += $discs_cd;
$total_discs_bd += $discs_bd;
$total_discs_3d += $discs_3d;
$total_discs_uhd += $discs_uhd;
$total_discs_hd += $discs_hd;
$total_discs_ld += $discs_ld;
$total_discs_vcd += $discs_vcd;
$total_discs_vhs += $discs_vhs;
$total_discs_dig += $discs_dig;

# print file summary
$td_tag = 'td';
$td = $td_tag . " class=\"total_row\"";
print " <${td} colspan=3>${total_items} (${total_items_dvd} DVD, ${total_items_cd} CD, ${total_items_bd} Blu-ray (${total_items_3d} 3D), ${total_items_uhd} UltraHD Blu-ray, ${total_items_hd} HD-DVD, ${total_items_ld} Laser Disc, ${total_items_vcd} VideoCD, ${total_items_vhs} VHS, ${total_items_dig} DigCopy)</${td_tag}> ";
if ($total_times) {
	$total_times = sprintf("%d day%s, %d:%02d&nbsp;(%d&nbsp;min.)", $total_times / 1440, (int($total_times / 1440) == 1) ? '' : 's', ($total_times / 60) % 24, $total_times % 60, $total_times);
	print "<${td}>${total_times}</${td_tag}> ";
} else {
	print "<${td}></td> ";
}
$total_dollars = sprintf("%0.2f", $total_dollars);
print "<${td} colspan=4>${total_discs} (${total_discs_dvd} DVD, ${total_discs_cd} CD, ${total_discs_bd} Blu-ray (${total_discs_3d} 3D), ${total_discs_uhd} UltraHD Blu-ray, ${total_discs_hd} HD-DVD, ${total_discs_ld} Laser Disc, ${total_discs_vcd} VideoCD, ${total_discs_vhs} VHS, ${total_discs_dig} DigCopy)</${td_tag}> <${td} colspan=1></${td_tag}> " . (defined($query{'cost'}) ? "<${td}>\$${total_dollars}</td> " : '') . "<${td} colspan=1></td> </tr>\n";
print "</tbody></table>\n";

search();

print <<EXPL unless defined($query{'raw'});
<p>
Common film aspect ratios are 1.37:1 (Full Frame), 1.66:1 (European 
and Disney productions), 1.85:1 (Matted Wide Screen) and 2.39:1 (Panavision, 
Anamorphic Widescreen). Historic film aspect ratios are 1.75:1 (Alternative 
Matted Wide Screen), 2.35:1 (Cinemascope, Techniscope), 2.20:1 (70mm) and 
2.76:1 (Ultra Panavision 70).
A DVD can encode pictures either "normal" with a 4:3 aspect ratio, or
anamorphic ("Enhanced for widescreen TVs") with a 16:9 aspect 
ratio. Video productions therefore usually have those 
aspect ratios, 1.33:1 and 1.78:1. Movies with 1.66:1, 1.85:1 
and 2.39:1 aspect ratios should be encoded as anamorphic 
video pictures, this increases the number of lines 
containing actual picture information. Movies shot on 
"Super 35" are intended to be shown at 2.39:1 or 1.85:1, 
but have enough image material above and below the frame 
to allow for a "fullscreen" 1.33:1 release as well.
</p>
<p>
The running time of PAL encoded movies is often shortened
due to the fact that the 24 fps film is encoded
without frame rate adaption on the 25 fps video 
system (2:2 pulldown). The duration time for PAL 
states the assumed film length in square brackets 
after the video running time.
</p>
<script type="text/javascript">
//<!--
//document.forms.Search.elements.search.focus();
//-->
</script>
EXPL

my $new_url_title_default = 'https://imdb.com/title//';
my $new_url_release_default = 'http://blu-ray.com/movies//';
my @new_fields = (
  'new_section', 
  'new_title', 
  'new_release', 
  'new_aspect_ratio', 
  'new_time', 
  'new_discs', 
  'new_std', 
  'new_lang', 
  'new_sub', 
  'new_url_title', 
  'new_date', 
  'new_cost', 
  'new_asin', 
  'new_url_release', 
  'new_timestamp', 
);
my %new_default = (
#  'new_time' => 'mmm or h:mm', 
  'new_url_title' => 'https://imdb.com/title//', 
  'new_url_release' => 'http://blu-ray.com/movies//', 
  'new_asin' => 'us/B000000000', 
);

foreach $name (@new_fields) {
  $new_default{$name} = '' unless defined($new_default{$name});
}

my $now = time();
my ($sec, $min, $hour, $day, $month, $year, $wday, $yday, $isdst) = gmtime($now);
$year += 1900;
$month++;
my $timestamp = sprintf("%04d%02d%02d%02d%02d%02d", $year, $month, $day, $hour, $min, $sec);
$new_default{'new_timestamp'} = $timestamp;
$new_default{'new_date'} = sprintf("%04d-%02d-%02d", $year, $month, $day);

my %new_entry = ();
my ($buffer, @pairs, $pair, $name, $value);
$ENV{'REQUEST_METHOD'} =~ tr/a-z/A-Z/;
if ($ENV{'REQUEST_METHOD'} eq "POST") {
  print "New entries:<br>\n";
  read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'});
  @pairs = split(/&/, $buffer);
  foreach $pair (@pairs) {
    ($name, $value) = split(/=/, $pair);
    $value =~ tr/+/ /;
    $value =~ s/%(..)/pack("C", hex($1))/eg;
    $new_entry{$name} = $value;
    $new_default{'new_date'} = $new_entry{'prev_default_date'} if ($new_entry{'prev_default_date'});
    if ($new_entry{$name} eq $new_default{$name}) {
      $new_entry{$name} = '';
    }
    print "&nbsp;&nbsp;'$name' = '$new_entry{$name}'<br>\n";
  }
  print "End of new entries<br>\n";
  # TODO
  
  $new_entry{'new_aspect_ratio'} = ($new_entry{'new_aspect_ratio'} ? $new_entry{'new_aspect_ratio'} : $new_entry{'new_aspect_select'}) . $new_entry{'new_aspect_format'};
  my $line = '';
  foreach $name (@new_fields) {
    $line .= $new_entry{$name} . "\t";
  }
  $line=~s/\t/|/g;
  print "New line: '$line'<br>\n";
}

my %default_entry = ();
foreach $name (keys %new_default) {
  $default_entry{$name} = $new_entry{$name} ? $new_entry{$name} : $new_default{$name} ? $new_default{$name} : '';
  #print "Def.: '$name' = '$default_entry{$name}'<br>\n";
}

print <<FORM unless defined($query{'raw'});
<p>
<form name="new_entry" method="post">
<a name="NewEntry"><h3>Add New Entry (under construction)</h3></a>
<table border=0>
<tr>
  <th class="alt_row" align="left">Item</th>
  <th class="alt_row" align="left">Value</th>
  <th class="alt_row" align="left">Format / Description</th>
</tr>
<tr>
  <td> Section:</td>
  <td><input type="text" name="new_section" value="$default_entry{'new_section'}" size="64" /></td>
  <td>Genre / Category</td>
</tr>
<tr>
  <td class="alt_row"> Title:</td>
  <td class="alt_row"><input type="text" name="new_title" value="$default_entry{'new_title'}" size="64" /></td>
  <td class="alt_row">Title of the Movie or Show, and Season (for shows)</td>
</tr>
<tr>
  <td> Year:</td>
  <td><input type="text" name="new_year" value="$default_entry{'new_year'}" size="4" /></td>
  <td>Year of first Release, YYYY</td>
</tr>
<tr>
  <td class="alt_row"> Release:</td>
  <td class="alt_row"><input type="text" name="new_release" value="$default_entry{'new_release'}" size="64" /></td>
  <td class="alt_row">Studio releasing the Media [ '-' Special Edition designation]</td>
</tr>
<tr>
  <td> URL (Title):</td>
  <td><input type="text" name="new_url_title"   value="$default_entry{'new_url_title'}" size="64" /></td>
  <td>URL about the Movie / Show (e.g. IMDB link)</td>
</tr>
<tr>
  <td class="alt_row"> URL (Release):</td>
  <td class="alt_row"><input type="text" name="new_url_release" value="$default_entry{'new_url_release'}" size="64" />  </td>
  <td class="alt_row">URL about the release (e.g. blu-ray.com link)</td>
</tr>
<tr>
  <td> Media Items:</td>
  <td><input type="text" name="new_discs"       value="$default_entry{'new_discs'}" size="12" /></td>
  <td>nX[nX[nX...]] n=Number, X=Media Type, one of [D|C|S|B|T|U|H|L|V|A|I|Y], 'D'=DVD, 'C'=CD, 'S'=SuperAudioCD, 'B'=Blu-ray, 'T'=Blu-ray 3D, 'U'=UltraHD Blu-ray, 'H'=HD-DVD, 'L'=Laser Disc, 'V'=Video CD, 'A'=UV/MA, 'I'=iTunes, 'Y'=other digital copy  </td>
</tr>
<tr>
  <td class="alt_row"> Standard and Region:</td>
  <td class="alt_row"><input type="text" name="new_std"         value="$default_entry{'new_std'}" size="3" /></td>
  <td class="alt_row">[SR], S=Standard, one of [N|P]: NTSC or PAL; R=Region, 0=Free, -=Copy, 1..8 for DVD, A..C for Blu-ray  </td>
</tr>
<tr>
  <td> Running Time:</td>
  <td><input type="text" name="new_time"        value="$default_entry{'new_time'}" size="6" /></td>
  <td>h:mm or mmm </td>
</tr>
<tr>
  <td class="alt_row"> Aspect Ratio:</td>
  <td class="alt_row"><input type="text" name="new_aspect_ratio"      value="$default_entry{'new_aspect_ratio'}" size="12" /></td>
  <td class="alt_row">'x.xx[f][/x.xx[f]...]' Aspect Ratio x.xx:1; f=Format, one of [ahipu]: none=Full-Format SD, a=Anamorphic SD, h=1080p HD, i=1080i HD, p=720p HD, u=2160p UHD  </td>
</tr>
<tr>
  <td> Audio:</td>
  <td><input type="text" name="new_lang"        value="$default_entry{'new_lang'}" size="12" /></td>
  <td>[LL|LLL[-CC]][([XX][C.L][ D])][/...] LL=ISO639-2, LLL=ISO639-3 <a href=\"${iso639_url}\" target=\"_blank\">Language Code</a>; CC=ISO3166 <a href=\"${iso3166_url}\" target=\"_blank\">Country Code</a>; XX=Audio Coding Type: PC=PCM, DD=Dolby Digital, DP=DDPlus, AD=DDPlus Atmos, TH=TrueHD, AT=Dolby TrueHD Atmos, DT=DTS, HR=DTS-HD High Resolution, MA=DTS-HD Master Audio, DX=DTS:X, DH=DTS Headphone:X, AU=Auro3D, MH=MPEG-H 3D Audio, AA=Advanced Audio Coding (AAC); C.L=Channels.LFE; D=Description, e.g. DVS, Descr., EX, latenight, Neo:X, etc.  </td>
</tr>
<tr>
  <td class="alt_row"> Subtiles:</td>
  <td class="alt_row"><input type="text" name="new_sub"         value="$default_entry{'new_sub'}" size="12" /></td>
  <td class="alt_row">[LL|LLL[-CC][(D)][/...]] LL=ISO639-2, LLL=ISO639-3 <a href=\"${iso639_url}\" target=\"_blank\">Language Code</a>; CC=ISO3166 <a href=\"${iso3166_url}\" target=\"_blank\">Country Code</a>; D=Description, e.g. SDH  </td>
</tr>
<tr>
  <td> Date:</td>
  <td><input type="text" name="new_date"        value="$default_entry{'new_date'}" size="11" /></td>
  <td>YYYY-MM-DD: Acquisition (i.e. arrival) Date</td>
</tr>
<tr>
  <td class="alt_row"> Cost:</td>
  <td class="alt_row"><input type="text" name="new_cost"        value="$default_entry{'new_cost'}" size="6" /></td>
  <td class="alt_row">Total cost, En.nn for Euro, Pn.nn for Pounds, otherwise Dollars</td>
</tr>
<tr>
  <td class="alt_row"> ASIN:</td>
  <td class="alt_row"><input type="text" name="new_asin"        value="$default_entry{'new_asin'}" size="14" /></td>
  <td class="alt_row">Amazon Product ID, prepended by country and a slash ('us/', 'de/', etc.) Default: US.</td>
</tr>
<tr>
  <td> Timestamp:</td>
  <td><input type="hidden" name="new_timestamp" value="$default_entry{'new_timestamp'}" />$default_entry{'new_timestamp'}</td>
  <td><input type="hidden" name="prev_default_date" value="$default_entry{'new_date'}" /></td>
</tr>
<tr>
  <td class="alt_row"></td>
  <td class="alt_row"><button type="submit">Add</button></td>
  <td class="alt_row"></td>
</tr>
</table>
</form>
</p>
FORM

my $aspect=<<ASP;
<tr><td> Aspect Ratio (future):</td><td>
  <select name="new_aspect_select" size="1" onSelect="document.forms.new_entry.elements.new_aspect_ratio.focus();">
    <option value="">Unknown</option>
    <option value="1.33">1.33:1, 4:3</option>
    <option value="1.37">1.37:1</option>
    <option value="1.66">1.66:1</option>
    <option selected value="1.78">1.78:1, 16:9</option>
    <option value="1.85">1.85:1</option>
    <option value="2.20">2.20:1</option>
    <option value="2.35">2.35:1</option>
    <option value="2.39">2.39:1, 2.40:1, 2.4:1</option>
    <option value="2.76">2.76:1</option>
    <option value="">Custom</option>
  </select>
  <input type="text" name="new_aspect_ratio" value="$default_entry{'new_aspect_ratio'}" size="4" />:1                
  <select name="new_aspect_format" size="1">
    <option value="">Unknown</option>
    <option value="">Full-Format SD</option>
    <option value="a">Anamorphic SD</option>
    <option selected value="h">1080p HD</option>
    <option value="i">1080i HD</option>
    <option value="p">720p HD</option>
    <option value="u">2160p UHD</option>
  </select>
</td></tr>
ASP

print "</body>\n";

