2025-06-05 18:35:10 +02:00
// The NuGet packages Microsoft.Data.Sqlite 9.0.0-9.0.5 throw an AccessViolationException and terminate the program when reading a "string" column with GetBytes()
// uncomment this #define when using Sqlite < 9.x or when MS fixed the error
//#define NoAccessViolationInSQLitePCLRaw
using System ;
2013-11-20 02:42:47 +01:00
using System.Collections.Generic ;
2013-04-12 00:47:50 +02:00
using System.Text ;
2021-04-25 18:31:05 +02:00
using System.Data ;
2013-04-12 00:47:50 +02:00
using ChanSort.Api ;
namespace ChanSort.Loader.Panasonic
{
internal class DbChannel : ChannelInfo
{
2013-11-20 02:42:47 +01:00
internal byte [ ] RawName ;
2020-12-26 18:16:01 +01:00
internal bool NonAscii ;
internal bool ValidUtf8 = true ;
2013-04-12 00:47:50 +02:00
2022-11-22 21:36:01 +01:00
internal int InternalProviderFlag2 ;
2023-06-03 10:38:11 +02:00
public int PhysicalChannel { get ; set ; }
/// <summary>
/// delivery number to seperate dvbc/dvbt/dvbs from cableip/antennaip/satip
/// </summary>
public int DeliveryType { get ; set ; }
2022-11-22 21:36:01 +01:00
#region ctor ( IDataReader , . . . )
2021-04-25 18:31:05 +02:00
internal DbChannel ( IDataReader r , IDictionary < string , int > field , DataRoot dataRoot , Encoding encoding )
2013-04-12 00:47:50 +02:00
{
this . RecordIndex = r . GetInt32 ( field [ "rowid" ] ) ;
this . RecordOrder = r . GetInt32 ( field [ "major_channel" ] ) ;
2013-06-27 20:21:56 +02:00
this . OldProgramNr = r . GetInt32 ( field [ "major_channel" ] ) ;
2019-08-29 16:57:20 +02:00
if ( this . OldProgramNr = = 1178 )
{
}
2013-04-12 00:47:50 +02:00
int ntype = r . GetInt32 ( field [ "ntype" ] ) ;
2023-06-03 10:38:11 +02:00
this . DeliveryType = r . GetInt32 ( field [ "delivery_type" ] ) ;
2013-04-12 00:47:50 +02:00
if ( ntype = = 1 )
2013-06-29 17:34:51 +02:00
{
2013-04-12 00:47:50 +02:00
this . SignalSource | = SignalSource . DvbS ;
2013-06-29 17:34:51 +02:00
if ( r . GetInt32 ( field [ "ya_svcid" ] ) > = 0 )
this . SignalSource | = SignalSource . Freesat ;
}
2013-04-12 00:47:50 +02:00
else if ( ntype = = 2 )
this . SignalSource | = SignalSource . DvbT ;
else if ( ntype = = 3 )
this . SignalSource | = SignalSource . DvbC ;
else if ( ntype = = 10 )
2013-06-29 17:34:51 +02:00
this . SignalSource | = SignalSource . AnalogT | SignalSource . Tv ;
else if ( ntype = = 14 )
this . SignalSource | = SignalSource . AnalogC | SignalSource . Tv ;
2015-01-14 21:38:01 +01:00
else if ( ntype = = 15 )
2023-06-03 10:38:11 +02:00
{
if ( this . DeliveryType = = 15 )
this . SignalSource | = SignalSource . IpSat ;
// else if (this.DeliveryType == 0) // currently no sample for IpAntenna found
// this.SignalSource |= SignalSource.IpAntenna;
else if ( this . DeliveryType = = 18 )
this . SignalSource | = SignalSource . IpCable ;
else
this . SignalSource | = SignalSource . IpSat ;
}
2013-04-12 00:47:50 +02:00
byte [ ] buffer = new byte [ 1000 ] ;
2021-09-23 14:58:42 +02:00
int len = 0 ;
2021-02-17 09:41:06 +01:00
if ( ! r . IsDBNull ( field [ "delivery" ] ) )
{
2021-09-23 14:58:42 +02:00
len = ( int ) r . GetBytes ( field [ "delivery" ] , 0 , buffer , 0 , 1000 ) ;
2021-02-17 09:41:06 +01:00
this . AddDebug ( buffer , 0 , ( int ) len ) ;
}
2013-04-12 00:47:50 +02:00
this . Skip = r . GetInt32 ( field [ "skip" ] ) ! = 0 ;
this . Encrypted = r . GetInt32 ( field [ "free_CA_mode" ] ) ! = 0 ;
this . Lock = r . GetInt32 ( field [ "child_lock" ] ) ! = 0 ;
this . ParseFavorites ( r , field ) ;
2013-11-20 02:42:47 +01:00
this . ReadNamesWithEncodingDetection ( r , field , encoding ) ;
2013-04-12 00:47:50 +02:00
2013-06-29 17:34:51 +02:00
if ( ntype = = 10 | | ntype = = 14 )
2013-04-12 00:47:50 +02:00
this . ReadAnalogData ( r , field ) ;
else
2021-09-23 14:58:42 +02:00
this . ReadDvbData ( r , field , dataRoot , buffer , len ) ;
2013-04-12 00:47:50 +02:00
}
#endregion
2022-11-22 21:36:01 +01:00
#region ctor ( SignalSource , . . . )
public DbChannel ( SignalSource signalSource , long id , int progNr , string name ) : base ( signalSource , id , progNr , name )
{
}
#endregion
2013-04-12 00:47:50 +02:00
#region ParseFavorites
2021-04-25 18:31:05 +02:00
private void ParseFavorites ( IDataReader r , IDictionary < string , int > field )
2013-04-12 00:47:50 +02:00
{
for ( int i = 0 ; i < 4 ; i + + )
{
int favIndex = r . GetInt32 ( field [ "profile" + ( i + 1 ) + "index" ] ) ;
if ( favIndex > 0 )
2013-07-02 23:55:02 +02:00
{
this . Favorites | = ( Favorites ) ( 1 < < i ) ;
2021-03-14 22:13:22 +01:00
this . SetOldPosition ( i + 1 , favIndex ) ;
2013-07-02 23:55:02 +02:00
}
2013-04-12 00:47:50 +02:00
}
}
#endregion
2013-11-20 02:42:47 +01:00
#region ReadAnalogData ( )
2021-04-25 18:31:05 +02:00
private void ReadAnalogData ( IDataReader r , IDictionary < string , int > field )
2013-04-12 00:47:50 +02:00
{
this . FreqInMhz = r . IsDBNull ( field [ "freq" ] ) ? 0 : ( decimal ) r . GetInt32 ( field [ "freq" ] ) / 1000 ;
this . ChannelOrTransponder = Tools . GetAnalogChannelNumber ( ( int ) this . FreqInMhz ) ;
2023-06-03 10:38:11 +02:00
this . PhysicalChannel = r . GetInt32 ( field [ "physical_ch" ] ) ;
this . OriginalNetworkId = r . GetInt32 ( field [ "onid" ] ) ;
this . TransportStreamId = r . GetInt32 ( field [ "tsid" ] ) ;
this . ServiceId = r . GetInt32 ( field [ "sid" ] ) ;
2013-04-12 00:47:50 +02:00
}
2013-11-20 02:42:47 +01:00
#endregion
2013-04-12 00:47:50 +02:00
#region ReadDvbData ( )
2021-09-23 14:58:42 +02:00
protected void ReadDvbData ( IDataReader r , IDictionary < string , int > field , DataRoot dataRoot , byte [ ] delivery , int deliveryLength )
2013-04-12 00:47:50 +02:00
{
int stype = r . GetInt32 ( field [ "stype" ] ) ;
2019-11-10 20:09:27 +01:00
this . SignalSource | = LookupData . Instance . IsRadioTvOrData ( stype ) ;
2013-04-12 00:47:50 +02:00
this . ServiceType = stype ;
2023-06-03 10:38:11 +02:00
decimal freq = r . IsDBNull ( field [ "freq" ] ) ? 0 : r . GetInt32 ( field [ "freq" ] ) ;
2013-04-12 00:47:50 +02:00
if ( ( this . SignalSource & SignalSource . Sat ) ! = 0 )
{
2023-06-03 10:38:11 +02:00
// SAT>IP has the freq value in units of 100Hz (so divide by 10k to get MHz), DVB>S has the value already in MHz
2013-04-21 22:04:06 +02:00
// ReSharper disable PossibleLossOfFraction
2023-06-03 10:38:11 +02:00
this . FreqInMhz = ( int ) ( freq / 10 ) ;
if ( this . FreqInMhz > 15000 ) // must be some satellite frequency in kHz instead of MHz, so convert
this . FreqInMhz / = 1000 ;
2013-04-21 22:04:06 +02:00
// ReSharper restore PossibleLossOfFraction
2021-09-23 14:58:42 +02:00
2021-09-23 15:24:21 +02:00
if ( deliveryLength > = 12 )
2021-09-23 14:58:42 +02:00
{
2021-09-23 20:25:30 +02:00
// Bytes 4-6 or 5-7 contain hex-encoded decimal digits for symbol rate. Bytes 10 and 11 are the sat orbital position.
// The byte-order varies between files and neither the endian-ness of the file header nor any other field gives a reliable clue which order is used.
// So I use bytes 0 and 3 as a heuristic to check for 0x01
2021-09-23 15:24:21 +02:00
2021-09-23 20:25:30 +02:00
// 01 14 92 99 00 21 99 90 02 31 01 92 00 00 00
var bigEndianSymbolRate = ( delivery [ 5 ] > > 4 ) * 10000 + ( delivery [ 5 ] & 0x0F ) * 1000 + // 21 99 90 => 21999
( delivery [ 6 ] > > 4 ) * 100 + ( delivery [ 6 ] & 0x0F ) * 10 + ( delivery [ 7 ] > > 4 ) ;
var bigEndianSatPosition = ( delivery [ 10 ] > > 4 ) * 1000 + ( delivery [ 10 ] & 0x0F ) * 100 + ( delivery [ 11 ] > > 4 ) * 10 + ( delivery [ 11 ] & 0x0F ) ; // 01 92 => 192
// 50 94 14 01 90 99 21 00 22 31 92 01 00 00 00 00 00
// 04 54 10 01 10 50 27 00 04 09 30 01 00 00 00
var littleEndianSymbolRate = ( delivery [ 6 ] > > 4 ) * 10000 + ( delivery [ 6 ] & 0x0F ) * 1000 + // 21 99 90 => 21999
( delivery [ 5 ] > > 4 ) * 100 + ( delivery [ 5 ] & 0x0F ) * 10 + ( delivery [ 4 ] > > 4 ) ;
var littleEndianSatPosition = ( delivery [ 11 ] > > 4 ) * 1000 + ( delivery [ 11 ] & 0x0F ) * 100 + ( delivery [ 10 ] > > 4 ) * 10 + ( delivery [ 10 ] & 0x0F ) ; // 92 01 => 192
bool useBigEndian = delivery [ 0 ] = = 1 ? delivery [ 3 ] ! = 1 | | bigEndianSymbolRate > = 4000 & & bigEndianSymbolRate < = 60000 & & ( bigEndianSymbolRate + 5 ) % 100 < = 10 : false ;
if ( useBigEndian )
2021-09-23 15:24:21 +02:00
{
2021-09-23 20:25:30 +02:00
this . SymbolRate = bigEndianSymbolRate ;
this . SatPosition = ( ( decimal ) bigEndianSatPosition / 10 ) . ToString ( "n1" ) ;
2021-09-23 15:24:21 +02:00
}
else
{
2021-09-23 20:25:30 +02:00
this . SymbolRate = littleEndianSymbolRate ;
this . SatPosition = ( ( decimal ) littleEndianSatPosition / 10 ) . ToString ( "n1" ) ;
2021-09-23 15:24:21 +02:00
}
2021-09-23 20:25:30 +02:00
this . Satellite = this . SatPosition ;
2013-04-12 00:47:50 +02:00
}
2021-09-23 14:58:42 +02:00
else
2013-04-12 00:47:50 +02:00
{
2021-09-23 14:58:42 +02:00
int satId = r . GetInt32 ( field [ "physical_ch" ] ) > > 12 ;
var sat = dataRoot . Satellites . TryGet ( satId ) ;
if ( sat ! = null )
{
this . Satellite = sat . Name ;
this . SatPosition = sat . OrbitalPosition ;
}
2013-04-12 00:47:50 +02:00
}
2021-09-23 14:58:42 +02:00
this . Source = "DVB-S" ;
2013-04-12 00:47:50 +02:00
}
else
{
freq / = 1000 ;
this . FreqInMhz = freq ;
2015-06-05 06:08:40 +02:00
this . ChannelOrTransponder = ( this . SignalSource & SignalSource . Antenna ) ! = 0 ?
LookupData . Instance . GetDvbtTransponder ( freq ) . ToString ( ) :
LookupData . Instance . GetDvbcTransponder ( freq ) . ToString ( ) ;
2021-09-23 14:58:42 +02:00
this . Source = ( this . SignalSource & SignalSource . Antenna ) ! = 0 ? "DVB-T" : "DVB-C" ;
2013-04-12 00:47:50 +02:00
}
2023-06-03 10:38:11 +02:00
this . PhysicalChannel = r . GetInt32 ( field [ "physical_ch" ] ) ;
2013-04-12 00:47:50 +02:00
this . OriginalNetworkId = r . GetInt32 ( field [ "onid" ] ) ;
this . TransportStreamId = r . GetInt32 ( field [ "tsid" ] ) ;
this . ServiceId = r . GetInt32 ( field [ "sid" ] ) ;
}
#endregion
2013-11-20 02:42:47 +01:00
#region ReadNamesWithEncodingDetection ( )
/// <summary>
/// Character encoding is a mess here. Code pages mixed with UTF-8 and raw data
/// </summary>
2021-04-25 18:31:05 +02:00
private void ReadNamesWithEncodingDetection ( IDataReader r , IDictionary < string , int > field , Encoding encoding )
2013-11-20 02:42:47 +01:00
{
2025-06-05 18:35:10 +02:00
#if NoAccessViolationInSQLitePCLRaw
// The NuGet packages Microsoft.Data.Sqlite 9.0.0-9.0.5 throw an AccessViolationException and terminate the program when reading a "string" column with GetBytes()
2025-03-08 18:19:10 +01:00
byte [ ] buffer = new byte [ 300 ] ;
int len = ( int ) r . GetBytes ( field [ "sname" ] , 0 , buffer , 0 , buffer . Length / 3 ) ;
#else
var str = r . GetString ( field [ "sname" ] ) ;
var buffer = Encoding . UTF8 . GetBytes ( str ) ;
var len = buffer . Length ;
#endif
2013-11-20 02:42:47 +01:00
this . RawName = new byte [ len ] ;
Array . Copy ( buffer , 0 , this . RawName , 0 , len ) ;
this . ChangeEncoding ( encoding ) ;
}
#endregion
#region ChangeEncoding ( )
public override void ChangeEncoding ( Encoding encoding )
{
// the encoding of channel names is a complete mess:
// it can be UTF-8
// it can be as specified by the DVB-encoding with a valid code page selector byte
// it can have a DVB-encoded code page selector, but ignores the CP and use UTF-8 regardless
// it can be code page encoded without any clue to what the code page is
// it can have DVB-control characters inside an UTF-8 stream
2021-09-22 23:46:03 +02:00
int len = Array . IndexOf < byte > ( this . RawName , 0 , 0 , this . RawName . Length ) ;
if ( len < 0 )
len = this . RawName . Length ;
if ( len = = 0 )
2013-11-20 02:42:47 +01:00
return ;
2021-09-22 23:46:03 +02:00
if ( ! GetRecommendedEncoding ( ref encoding , out var startOffset , out var bytesPerChar ) )
2013-11-20 02:42:47 +01:00
return ;
// single byte code pages might have UTF-8 code mixed in, so we have to parse it manually
StringBuilder sb = new StringBuilder ( ) ;
2020-12-26 18:16:01 +01:00
this . NonAscii = false ;
this . ValidUtf8 = true ;
2021-09-22 23:46:03 +02:00
for ( int i = startOffset ; i < len ; i + = bytesPerChar )
2013-11-20 02:42:47 +01:00
{
byte c = this . RawName [ i ] ;
2021-09-22 23:46:03 +02:00
byte c2 = i + 1 < len ? this . RawName [ i + 1 ] : ( byte ) 0 ;
byte c3 = i + 2 < len ? this . RawName [ i + 2 ] : ( byte ) 0 ;
byte c4 = i + 4 < len ? this . RawName [ i + 3 ] : ( byte ) 0 ;
2020-12-26 18:16:01 +01:00
if ( c > = 0x80 )
NonAscii = true ;
if ( c < 0x80 )
sb . Append ( ( char ) c ) ;
else if ( c < 0xA0 )
{
ValidUtf8 = false ;
sb . Append ( ( char ) c ) ;
}
2021-09-22 23:46:03 +02:00
else if ( bytesPerChar = = 1 )
2013-11-20 02:42:47 +01:00
{
2021-09-22 23:46:03 +02:00
if ( c > = 0xC0 & & c < = 0xDF & & c2 > = 0x80 & & c2 < = 0xBF ) // 2 byte UTF-8
{
sb . Append ( ( char ) ( ( ( c & 0x1F ) < < 6 ) | ( c2 & 0x3F ) ) ) ;
+ + i ;
}
else if ( c > = 0xE0 & & c < = 0xEF & & ( c2 & 0xC0 ) = = 0x80 & & ( c3 & 0xC0 ) = = 0x80 ) // 3 byte UTF-8
{
sb . Append ( ( char ) ( ( ( c & 0x0F ) < < 12 ) | ( ( c2 & 0x3F ) < < 6 ) | ( c3 & 0x3F ) ) ) ;
i + = 2 ;
}
else if ( c > = 0xF0 & & c < = 0xF7 & & ( c2 & 0xC0 ) = = 0x80 & & ( c3 & 0xC0 ) = = 0x80 & & ( c4 & 0xC0 ) = = 0x80 ) // 4 byte UTF-8
{
sb . Append ( ( char ) ( ( ( c & 0x07 ) < < 18 ) | ( ( c2 & 0x3F ) < < 12 ) | ( ( c3 & 0x3F ) < < 6 ) | ( c4 & 0x3F ) ) ) ;
i + = 3 ;
}
else
{
ValidUtf8 = false ;
sb . Append ( encoding . GetString ( this . RawName , i , bytesPerChar ) ) ;
}
2013-11-20 02:42:47 +01:00
}
else
2020-12-26 18:16:01 +01:00
{
ValidUtf8 = false ;
2013-11-20 02:42:47 +01:00
sb . Append ( encoding . GetString ( this . RawName , i , bytesPerChar ) ) ;
2020-12-26 18:16:01 +01:00
}
2013-11-20 02:42:47 +01:00
}
2021-09-22 23:46:03 +02:00
this . GetChannelNames ( sb . ToString ( ) , out var longName , out var shortName ) ;
2013-11-20 02:42:47 +01:00
this . Name = longName ;
this . ShortName = shortName ;
}
#endregion
#region GetRecommendedEncoding ( )
private bool GetRecommendedEncoding ( ref Encoding encoding , out int startOffset , out int bytesPerChar )
{
startOffset = 0 ;
bytesPerChar = 1 ;
if ( RawName [ 0 ] < 0x10 ) // single byte character sets
{
encoding = DvbStringDecoder . GetEncoding ( RawName [ 0 ] ) ;
startOffset = 1 ;
}
else if ( RawName [ 0 ] = = 0x10 ) // prefix for 16 bit code page ID with single byte character sets
{
if ( RawName . Length < 3 ) return false ;
encoding = DvbStringDecoder . GetEncoding ( 0x100000 + RawName [ 1 ] * 256 + RawName [ 2 ] ) ;
startOffset = 3 ;
}
else if ( RawName [ 0 ] = = 0x15 ) // UTF-8
{
encoding = Encoding . UTF8 ;
startOffset = 1 ;
}
else if ( RawName [ 0 ] < 0x20 ) // various 2-byte character sets
{
encoding = DvbStringDecoder . GetEncoding ( RawName [ 0 ] ) ;
startOffset = 1 ;
bytesPerChar = 2 ;
}
return true ;
}
#endregion
2013-04-12 00:47:50 +02:00
#region GetChannelNames ( )
private void GetChannelNames ( string name , out string longName , out string shortName )
{
StringBuilder sbLong = new StringBuilder ( ) ;
StringBuilder sbShort = new StringBuilder ( ) ;
bool inShort = false ;
foreach ( char c in name )
{
if ( c < 0x20 )
continue ;
2013-11-20 02:42:47 +01:00
if ( c = = 0x86 | | c = = ' \ uE086 ' )
2013-04-12 00:47:50 +02:00
inShort = true ;
2013-11-20 02:42:47 +01:00
else if ( c = = 0x87 | | c = = ' \ uE087 ' )
2013-04-12 00:47:50 +02:00
inShort = false ;
2013-11-20 02:42:47 +01:00
if ( c > = 0x80 & & c < = 0x9F | | c > = ' \ uE080 ' & & c < = ' \ uE09F ' )
2013-04-12 00:47:50 +02:00
continue ;
if ( inShort )
sbShort . Append ( c ) ;
sbLong . Append ( c ) ;
}
longName = sbLong . ToString ( ) ;
shortName = sbShort . ToString ( ) ;
}
#endregion
2020-12-26 16:51:33 +01:00
#region UpdateRawData ( )
2020-12-26 18:16:01 +01:00
public void UpdateRawData ( bool explicitUtf8 , bool implicitUtf8 )
2020-12-26 16:51:33 +01:00
{
if ( IsNameModified )
{
var utf8 = Encoding . UTF8 . GetBytes ( this . Name ) ;
2020-12-26 18:16:01 +01:00
if ( implicitUtf8 )
this . RawName = utf8 ;
else if ( explicitUtf8 )
{
this . RawName = new byte [ utf8 . Length + 1 ] ;
this . RawName [ 0 ] = 0x15 ; // DVB encoding ID for UTF8
Array . Copy ( utf8 , 0 , this . RawName , 1 , utf8 . Length ) ;
}
2020-12-26 16:51:33 +01:00
}
}
#endregion
2013-04-12 00:47:50 +02:00
}
}