2021-03-16 11:41:59 +01:00
using System ;
using System.Collections.Generic ;
2021-03-15 20:53:45 +01:00
using System.IO ;
using System.Text ;
using ChanSort.Api ;
namespace ChanSort.Loader.Sharp
{
/ *
* This loader supports multiple . csv formats from different brands , which are similar but have some differences :
*
*
* DVBS_Program . csv + cvt_database . dat
* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
* These formats lack a way to uniquely identify a channel via ONID - TSID - SID and as described in their file header ,
* can only be physically reordered to change the zapping order , but don ' t allow changing the channel number :
2021-07-23 21:44:02 +02:00
* - Hisense LTDN40D50TS ( LCN is a bogus column name and is actually the physical channel record index in the . dat file ) : LCN , Channel Name , Service Type , [ B ]
* - Hisense 40 A5100F ( DTV , Radio , Data all start at LCN 1 ) : Channel Number , LCN , Channel Name , Service Type , Free or Scramble , Transponder , [ S ]
2021-03-15 20:53:45 +01:00
* - Sharp LC - 43 CFE4142E firmware V1 . 16 : Channel Number , Channel Name , Service Type , Free or Scramble , Transponder , [ S ]
* - Dyon Live 24 Pro , Dyon ENTER 32 Pro X : Channel Number , Channel Name , Service Type , Free or Scramble , Transponder , [ S ]
* - Blaupunkt B32B133T2CSHD : Channel Number , Channel Name , Service Type , Free or Scramble , Transponder , [ S ]
*
* MS6486_DVBS_CHANNEL_TABLE + MS6488_HOTELMODE_TABLE . json
* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
2021-07-23 21:44:02 +02:00
* - Channel Number , Channel Name , Service Type , Free or Scramble , Frequency ( MHz ) , Polarity , SymbolRate ( KS / s ) , [ S ]
2021-03-15 20:53:45 +01:00
*
*
* DVBS_CHANNEL_TABLE . csv + dtv_cmdb_ * . bin
* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
* This format supposedly supports deleting and changing the program numbers along with reordering ( despite the NOTE in the header ) .
* - Channel number , Channel Name , program count , program index , RF channel number , QAM mode , Band width , PlpID , Frequency , symbol rate , Polarity , SatName , SatID , SatTableID , LowLOF , HighLOF , LNBType , LNBTypeReal , DISEQC level , ToneburstType , Swt10Port , Swt11Port , 22 KOnOff , LNB power , 12 VOnOff , Motor position bit8 1 : USALS 0 : DISEQC1 . 2 , Satellite Angle , Transponder number , Begin transponder , channel Id , unicable frequency , unicable MDU , unicable password pin , LNB index 0 : A 1 : B , TS id , orig network id , network id , PCR pid , LCN , Free or Scramble , PmtPID , ServiceID , Video_pid , Audio_pid , VideoType , AudioType , NitVer , PatVer , PmtVer , SdtVer , Service Type , [ S ]
*
* /
internal class SharpSerializer : SerializerBase
{
private enum FormatVersion
{
Hisense3Columns = 1 ,
Sharp5Columns = 2 ,
2021-07-23 21:44:02 +02:00
Hisense6Columns = 3 ,
Sharp7Columns = 4 ,
Sharp51Columns = 5
2021-03-15 20:53:45 +01:00
}
2021-07-23 21:44:02 +02:00
private readonly ChannelList dtvChannels = new ChannelList ( SignalSource . DvbS | SignalSource . Tv , "DTV" ) ;
private readonly ChannelList radioChannels = new ChannelList ( SignalSource . DvbS | SignalSource . Radio , "Radio" ) ;
private readonly ChannelList dataChannels = new ChannelList ( SignalSource . DvbS | SignalSource . Data , "Data" ) ;
2021-03-15 20:53:45 +01:00
private Encoding encoding ;
private FormatVersion formatVersion ;
2021-03-16 11:41:59 +01:00
private string [ ] cols ;
2021-03-15 20:53:45 +01:00
private string [ ] lines ;
#region ctor ( )
public SharpSerializer ( string inputFile ) : base ( inputFile )
{
this . Features . ChannelNameEdit = ChannelNameEditMode . None ;
this . Features . DeleteMode = DeleteMode . Physically ;
this . Features . CanSkipChannels = false ;
this . Features . CanLockChannels = false ;
this . Features . CanHideChannels = false ;
2021-03-16 11:41:59 +01:00
this . Features . CanHaveGaps = false ;
2021-03-15 20:53:45 +01:00
this . Features . FavoritesMode = FavoritesMode . None ;
2021-07-23 21:44:02 +02:00
this . DataRoot . AddChannelList ( this . dtvChannels ) ;
this . DataRoot . AddChannelList ( this . radioChannels ) ;
this . DataRoot . AddChannelList ( this . dataChannels ) ;
2021-03-15 20:53:45 +01:00
}
#endregion
#region Load ( )
public override void Load ( )
{
var content = File . ReadAllBytes ( this . FileName ) ;
this . encoding = Tools . IsUtf8 ( content ) ? new UTF8Encoding ( false ) : Encoding . GetEncoding ( 1252 ) ;
this . lines = this . encoding . GetString ( content ) . Replace ( "\r" , "" ) . Split ( '\n' ) ;
2021-03-17 07:01:05 +01:00
if ( lines . Length > 2 )
this . cols = lines [ 2 ] . ToLowerInvariant ( ) . Split ( ',' ) ;
2021-03-15 20:53:45 +01:00
this . formatVersion = DetectFormatVersion ( ) ;
this . AdjustVisibleColumns ( ) ;
for ( int i = 3 ; i < this . lines . Length ; i + + )
{
var line = lines [ i ] ;
if ( line = = "" )
continue ;
var data = line . Split ( ',' ) ;
if ( data [ 0 ] = = "[E]" )
break ;
2021-07-23 21:44:02 +02:00
ReadChannel ( i , data ) ;
}
}
#endregion
#region ReadChannel ( )
private void ReadChannel ( int i , string [ ] data )
{
var ch = new ChannelInfo ( SignalSource . DvbS , i , 0 , "" ) ;
for ( int j = 0 ; j < data . Length ; j + + )
{
var val = data [ j ] ;
int . TryParse ( val , out var intval ) ;
switch ( cols [ j ] )
2021-03-15 20:53:45 +01:00
{
2021-07-23 21:44:02 +02:00
case "lcn" :
if ( this . formatVersion = = FormatVersion . Hisense3Columns ) // this format incorrectly labels the physical channel index column as "LCN"
2021-03-15 20:53:45 +01:00
ch . RecordOrder = intval ;
2021-07-23 21:44:02 +02:00
else if ( this . formatVersion ! = FormatVersion . Sharp51Columns ) // the Sharp51 format has "channel number", "program index" with valid numbers and "LCN" with all values 65535
ch . OldProgramNr = intval ;
break ;
case "channel number" :
ch . RecordOrder = intval ;
if ( this . formatVersion = = FormatVersion . Sharp51Columns )
ch . OldProgramNr = intval ;
break ;
case "channel name" :
ch . Name = val ;
break ;
case "service type" :
// some files use int values, other use strings
if ( intval ! = 0 )
{
if ( intval = = 1 )
2021-03-15 20:53:45 +01:00
{
2021-07-23 21:44:02 +02:00
ch . SignalSource | = SignalSource . Tv ;
ch . ServiceTypeName = "DTV" ;
2021-03-15 20:53:45 +01:00
}
2021-07-23 21:44:02 +02:00
else if ( intval = = 2 )
{
ch . SignalSource | = SignalSource . Radio ;
ch . ServiceTypeName = "Radio" ;
}
else if ( intval = = 3 )
{
ch . SignalSource | = SignalSource . Data ;
ch . ServiceTypeName = "Data" ;
}
}
else
2021-03-15 20:53:45 +01:00
{
2021-07-23 21:44:02 +02:00
ch . ServiceTypeName = val ;
var lval = val . ToLowerInvariant ( ) ;
if ( lval = = "dtv" )
ch . SignalSource | = SignalSource . Tv ;
else if ( lval = = "radio" )
ch . SignalSource | = SignalSource . Radio ;
else
ch . SignalSource | = SignalSource . Data ;
2021-03-15 20:53:45 +01:00
}
2021-07-23 21:44:02 +02:00
break ;
case "free or scramble" :
ch . Encrypted = val = = "Scramble" | | val = = "1" ;
break ;
case "transponder" :
{
var parts = val . Split ( ' ' ) ;
if ( int . TryParse ( parts [ 0 ] , out var mhz ) )
ch . FreqInMhz = mhz ;
if ( parts . Length > 1 & & parts [ 1 ] . Length > 0 )
ch . Polarity = parts [ 1 ] [ 0 ] ;
if ( parts . Length > 2 & & int . TryParse ( parts [ 2 ] , out var sr ) )
ch . SymbolRate = sr ;
break ;
2021-03-15 20:53:45 +01:00
}
2021-07-23 21:44:02 +02:00
case "frequency(mhz)" :
case "frequency" :
ch . FreqInMhz = intval ;
break ;
case "polarity" :
if ( val . Length > 0 )
ch . Polarity = val [ 0 ] = = '0' | | val [ 0 ] = = 'H' ? 'H' : 'V' ;
break ;
case "symbolrate(ks/s)" :
case "symbol rate" :
ch . SymbolRate = intval ;
break ;
case "satname" :
ch . Satellite = val ;
break ;
case "satellite angle" :
ch . SatPosition = val ;
break ;
case "orig network id" :
ch . OriginalNetworkId = intval ;
break ;
case "ts id" :
ch . TransportStreamId = intval ;
break ;
case "serviceid" :
ch . ServiceId = intval ;
break ;
case "pcr pid" :
ch . PcrPid = intval ;
break ;
case "video_pid" :
ch . VideoPid = intval ;
break ;
case "audio_pid" :
ch . AudioPid = intval ;
break ;
2021-03-15 20:53:45 +01:00
}
}
2021-07-23 21:44:02 +02:00
var list = this . GetChannelList ( ch ) ;
if ( ch . OldProgramNr = = 0 ) // if there was no explicit LCN, channels are automatically numbered sequentially by order in their list
ch . OldProgramNr = list . Count + 1 ;
this . DataRoot . AddChannel ( list , ch ) ;
2021-03-15 20:53:45 +01:00
}
2021-07-23 21:44:02 +02:00
2021-03-15 20:53:45 +01:00
#endregion
#region DetectFormatVersion ( )
private FormatVersion DetectFormatVersion ( )
{
if ( lines . Length > = 3 )
{
if ( lines [ 0 ] . StartsWith ( "--------S2 Program Data!--------" ) & & lines [ 2 ] = = "LCN,Channel Name,Service Type,[B]" )
return FormatVersion . Hisense3Columns ;
if ( lines [ 0 ] . StartsWith ( "--------DVBS Program Data!--------" ) )
{
if ( lines [ 2 ] = = "Channel Number,Channel Name,Service Type,Free or Scramble,Transponder,[S]" )
return FormatVersion . Sharp5Columns ;
2021-07-23 21:44:02 +02:00
if ( lines [ 2 ] = = "Channel Number,LCN,Channel Name,Service Type,Free or Scramble,Transponder,[S]" )
return FormatVersion . Hisense6Columns ;
2021-03-15 20:53:45 +01:00
if ( lines [ 2 ] = = "Channel Number,Channel Name,Service Type,Free or Scramble,Frequency(MHz),Polarity,SymbolRate(KS/s),[S]" )
return FormatVersion . Sharp7Columns ;
// fallback for formats with more information, as long as they contain the required columns
var dict = new HashSet < string > ( ) ;
foreach ( var col in cols )
dict . Add ( col ) ;
if ( dict . Contains ( "channel number" ) & & dict . Contains ( "channel name" ) & & dict . Contains ( "service type" ) & & dict . Contains ( "free or scramble" ) )
{
if ( dict . Contains ( "orig network id" ) & & dict . Contains ( "ts id" ) & & dict . Contains ( "serviceid" ) )
return FormatVersion . Sharp51Columns ;
return FormatVersion . Sharp5Columns ;
}
}
}
2022-11-29 14:56:23 +01:00
throw LoaderException . Fail ( "File does not contain the expected 3 header lines" ) ;
2021-03-15 20:53:45 +01:00
}
#endregion
#region AdjustVisibleColumns ( )
private void AdjustVisibleColumns ( )
{
2021-07-23 21:44:02 +02:00
foreach ( var list in this . DataRoot . ChannelLists )
2021-03-15 20:53:45 +01:00
{
2021-07-23 21:44:02 +02:00
list . VisibleColumnFieldNames . Clear ( ) ;
list . VisibleColumnFieldNames . Add ( "Position" ) ;
list . VisibleColumnFieldNames . Add ( "OldPosition" ) ;
list . VisibleColumnFieldNames . Add ( nameof ( ChannelInfo . RecordOrder ) ) ;
list . VisibleColumnFieldNames . Add ( nameof ( ChannelInfo . Name ) ) ;
if ( this . formatVersion > = FormatVersion . Sharp5Columns )
{
list . VisibleColumnFieldNames . Add ( nameof ( ChannelInfo . Encrypted ) ) ;
list . VisibleColumnFieldNames . Add ( nameof ( ChannelInfo . FreqInMhz ) ) ;
list . VisibleColumnFieldNames . Add ( nameof ( ChannelInfo . Polarity ) ) ;
list . VisibleColumnFieldNames . Add ( nameof ( ChannelInfo . SymbolRate ) ) ;
}
2021-03-15 20:53:45 +01:00
2021-07-23 21:44:02 +02:00
if ( this . formatVersion > = FormatVersion . Sharp51Columns )
{
list . VisibleColumnFieldNames . Add ( nameof ( ChannelInfo . Satellite ) ) ;
list . VisibleColumnFieldNames . Add ( nameof ( ChannelInfo . OriginalNetworkId ) ) ;
list . VisibleColumnFieldNames . Add ( nameof ( ChannelInfo . TransportStreamId ) ) ;
list . VisibleColumnFieldNames . Add ( nameof ( ChannelInfo . ServiceId ) ) ;
list . VisibleColumnFieldNames . Add ( nameof ( ChannelInfo . PcrPid ) ) ;
list . VisibleColumnFieldNames . Add ( nameof ( ChannelInfo . AudioPid ) ) ;
list . VisibleColumnFieldNames . Add ( nameof ( ChannelInfo . VideoPid ) ) ;
}
list . VisibleColumnFieldNames . Add ( nameof ( ChannelInfo . ServiceTypeName ) ) ;
2021-03-15 20:53:45 +01:00
}
}
#endregion
2021-07-23 21:44:02 +02:00
#region GetChannelList ( )
private ChannelList GetChannelList ( ChannelInfo channel )
{
switch ( channel . SignalSource & SignalSource . MaskTvRadioData )
{
case SignalSource . Tv : return dtvChannels ;
case SignalSource . Radio : return radioChannels ;
default : return dataChannels ;
}
}
#endregion
2021-03-15 20:53:45 +01:00
#region Save ( )
2022-11-29 22:00:16 +01:00
public override void Save ( )
2021-03-15 20:53:45 +01:00
{
2022-11-29 22:00:16 +01:00
using var file = new StreamWriter ( new FileStream ( this . FileName , FileMode . Create ) , this . encoding ) ;
2021-03-15 20:53:45 +01:00
// write original header
for ( int i = 0 ; i < 3 ; i + + )
file . WriteLine ( this . lines [ i ] ) ;
2021-03-16 11:41:59 +01:00
// index of fields in the extended FormatVersion.Sharp51Columns
var ixChannelNumber = Array . IndexOf ( this . cols , "channel number" ) ;
var ixProgramIndex = ixChannelNumber < 0 ? - 1 : Array . IndexOf ( this . cols , "program index" ) ;
2021-07-23 21:44:02 +02:00
var ixLcn = ixChannelNumber < 0 ? - 1 : Array . IndexOf ( this . cols , "lcn" ) ;
2021-03-16 11:41:59 +01:00
2021-07-23 21:44:02 +02:00
foreach ( var channelList in new [ ] { dtvChannels , radioChannels , dataChannels } )
2021-03-15 20:53:45 +01:00
{
2021-07-23 21:44:02 +02:00
foreach ( var channel in channelList . GetChannelsByNewOrder ( ) )
2021-03-16 11:41:59 +01:00
{
2021-07-23 21:44:02 +02:00
// when a reference list was applied, the list may contain proxy entries for deleted channels, which must be ignored
if ( channel . IsProxy | | channel . IsDeleted )
continue ;
var line = this . lines [ channel . RecordIndex ] ;
if ( ixProgramIndex > = 0 )
{
// this extended format would only change the zapping order unless the "Channel Number" and "Channel Index" fields are updated too
var fields = this . lines [ channel . RecordIndex ] . Split ( ',' ) ;
fields [ ixChannelNumber ] = fields [ ixProgramIndex ] = channel . NewProgramNr . ToString ( ) ;
line = string . Join ( "," , fields ) ;
}
else if ( ixLcn > = 0 )
{
// Hisense 6 column format with Channel Number, LCN, ...
var fields = this . lines [ channel . RecordIndex ] . Split ( ',' ) ;
fields [ ixLcn ] = channel . NewProgramNr . ToString ( ) ;
line = string . Join ( "," , fields ) ;
}
else
{
// the older formats require the "Channel Number" to be unchanged and update it internally during the import based on the order of the lines
}
file . WriteLine ( line ) ;
2021-03-16 11:41:59 +01:00
}
2021-03-15 20:53:45 +01:00
}
file . WriteLine ( "[E]" ) ;
}
#endregion
#region GetFileInformation ( )
public override string GetFileInformation ( )
{
var sb = new StringBuilder ( ) ;
sb . Append ( base . GetFileInformation ( ) ) ;
sb . AppendLine ( ) ;
sb . AppendLine ( "Columns in CSV file:" ) ;
sb . AppendLine ( this . lines [ 2 ] ) ;
return sb . ToString ( ) ;
}
#endregion
}
}