Jump to content
majstang

COM EPGManager.EPGNow and EPGNext

Recommended Posts

majstang

Hello!

 

I have been searching the scripting lounge after an easy way to get EPGNow (Time and Title) and EPGNext (Time and Title) for other channels besides the current tuned one. But these methods does only apply for the current channel as it seems. It is somewhere there things gets complicated. Would have been a dreamscenario if passing a channel name or channelID would be enough to the EPGNow and EPGNext and get that Now and next show info. I suspect I need fetch the whole GetAsArray and start calculating from there, right? 

Some AHK:

iDVBViewer := ComObjActive("DVBViewerServer.DVBViewer")
EPGNow_Time := iDVBViewer.EPGManager.EPGNow.Time
EPGNow_Title := iDVBViewer.EPGManager.EPGNow.Title
EPGNext_Time := iDVBViewer.EPGManager.EPGNext.Time
EPGNext_Title := iDVBViewer.EPGManager.EPGNext.Title
;msgbox % "Channel= " title "`n" EPGNow_Time . "  " . EPGNow_Title . " `n " . EPGNext_Time "  " EPGNext_Title " "

 

Share this post


Link to post
Marc32

hi,

 

thats not so complicated. unfortunately all i can do is some vbscript.

 

'___________________________________________________________

Set DVBViewer = GetObject(, "DVBViewerServer.DVBViewer")
channelnumber = 2     'number of your wanted channel

epgcid = DVBViewer.channelmanager(channelnumber).EPGChannelID
SuD = DVBViewer.channelmanager.GetByEPGChannelID(epgcid).tuner.SID
TuD = DVBViewer.channelmanager.GetByEPGChannelID(epgcid).tuner.TransportStreamID
epgnow = DVBViewer.epgmanager.Get(SuD,TuD,0,0).item(0).Title
epgnext = DVBViewer.epgmanager.Get(SuD,TuD,0,0).item(1).Title
starttime = DVBViewer.epgmanager.Get(SuD,TuD,0,0).item(0).Time
endtime = DVBViewer.epgmanager.Get(SuD,TuD,0,0).item(0).Endtime
duration = DVBViewer.epgmanager.Get(SuD,TuD,0,0).item(0).Duration
showinfo = DVBViewer.epgmanager.Get(SuD,TuD,0,0).item(0).Description

MsgBox epgnow
MsgBox epgnext
msgbox starttime
msgbox endtime
MsgBox duration
MsgBox showinfo

'______________________________________________________________

 

you have to pass the channelnumber this time, because getbychannelname is buggy atm.

griga knows about it and will fix it (hopefully next release).

 

anyway, the channelnumber is as good as the channelname, i think.

 

hope, this will help you a lil bit

marc

 


 

 

 

Edited by Marc32

Share this post


Link to post
majstang

Hello Marc!

 

Mighty thanks:D

Hmm...it seems as if Im having some EPG trouble (XEPG imported xmltv). This method returns the wrong EPG entry from the past:
2017-05-03 23:20:00  Blaaaa
2017-05-04 00:50:00  Blaaaaha

I may go back to DVBV.5, RS and XEPG .6 to see if the EPG changes of late has something to do with it.

iDVBViewer := ComObjActive("DVBViewerServer.DVBViewer")
channelnumber := 0 ; number of your wanted channel

epgcid := iDVBViewer.channelmanager(channelnumber).EPGChannelID
SuD := iDVBViewer.channelmanager.GetByEPGChannelID(epgcid).tuner.SID
TuD := iDVBViewer.channelmanager.GetByEPGChannelID(epgcid).tuner.TransportStreamID
epgnow := iDVBViewer.epgmanager.Get(SuD,TuD,0,0).item(0).Title
epgnext := iDVBViewer.epgmanager.Get(SuD,TuD,0,0).item(1).Title
starttime := iDVBViewer.epgmanager.Get(SuD,TuD,0,0).item(0).Time
endtime := iDVBViewer.epgmanager.Get(SuD,TuD,0,0).item(0).Endtime
duration := iDVBViewer.epgmanager.Get(SuD,TuD,0,0).item(0).Duration
showinfo := iDVBViewer.epgmanager.Get(SuD,TuD,0,0).item(0).Description

msgbox % starttime . "  " . epgnow . "`n" . endtime "  " epgnext " "


 

Edited by majstang

Share this post


Link to post
Marc32

what happens if you specify the time? pls try this for epgnow.

 

epgnow = DVBViewer.epgmanager.Get(SuD,TuD,now,now).item(0).Title

 

and remember you have to change item(0) to item(1) or item(2) or ......... for entries in the future. 

 

 

 

Edited by Marc32

Share this post


Link to post
majstang

Im not sure which time format Get requires:unsure:

"now" does not work by the way. The AHK equivalent is a_now which returns the current local time in YYYYMMDDHH24MISS format. Using that string in the GET call gives error. 

Edited by majstang

Share this post


Link to post
Marc32

hmm, now works for me:) it returns the right epgentry

 

ahh, pls try without  " "

Edited by Marc32

Share this post


Link to post
majstang

not for me:( 

Share this post


Link to post
nuts

I think "Get" requires the delphi "TDateTime" format.

I am using this functions (autoit) for Tdate und Ttime (TDateTime = Tdate + Ttime):

Func _Time_to_Ttime($stime)
	; nuts (www.autoit.de)
	;
	; $stime = HH:MM:SS
	; return = TDate conform flootingpoint number (i.e.  starttime to add a timer)
	; @error = 1  => no valid time input

	If StringRegExp($stime, '\A([0-1][0-9]|[2][0-3])\:[0-5][0-9]\:[0-5][0-9]\z') Then
		Local $asplit = StringSplit($stime, ":")
		Return $asplit[1] / 24 + $asplit[2] / 1440 + $asplit[3] / 86400
	Else
		Return SetError(1, 0, 0)
	EndIf
EndFunc   ;==>_Time_to_Ttime

Func _Date_to_Tdate($sdate)
	; nuts (www.autoit.de)
	;
	; $sdate = YYYY/MM/DD
	; return = TDate conform flootingpoint number (i.e.  date to add a timer)
	; @error = 1  => no valid date input
	If _DateIsValid($sdate) Then
		Local $asplit = StringSplit($sdate, "/")
		Local $iDays = _DateToDayValue($asplit[1], $asplit[2], $asplit[3])
		$iDays -= 2415018.5
		Return $iDays
	Else
		Return SetError(1, 0, 0)
	EndIf

EndFunc   ;==>_Date_to_Tdate

 

Share this post


Link to post
majstang

Great, that was what I needed:) Thanks nuts!

Im sure I will get this working now.

Share this post


Link to post
majstang

For VBScript I had to do it like this, which works:

channelnumber = 0 'number of your wanted channel

epgcid = iDVBViewer.channelmanager(channelnumber).EPGChannelID
SuD = iDVBViewer.channelmanager.GetByEPGChannelID(epgcid).tuner.SID
TuD = iDVBViewer.channelmanager.GetByEPGChannelID(epgcid).tuner.TransportStreamID
epgnow = iDVBViewer.epgmanager.Get(SuD,TuD,now,now).item(0).Title
epgnext = iDVBViewer.epgmanager.Get(SuD,TuD,now,endtime).item(1).Title
starttime = iDVBViewer.epgmanager.Get(SuD,TuD,now,now).item(0).Time
endtime = iDVBViewer.epgmanager.Get(SuD,TuD,now,endtime).item(0).Endtime
duration = iDVBViewer.epgmanager.Get(SuD,TuD,now,now).item(0).Duration
showinfo = iDVBViewer.epgmanager.Get(SuD,TuD,now,now).item(0).Description

MsgBox epgnow
MsgBox epgnext
msgbox starttime
msgbox endtime
MsgBox duration
MsgBox showinfo

Now, let see if this can be done with AutoHotkey

Share this post


Link to post
majstang

Hi nuts!

 

Hmm, I cant get the exact value the Tdatetime is requiring with your code nuts! The now calculations isnt even close, 12hrs off into the future. With a little manual tweaking I can come as close as 3hrs future time. This was tricky to say the least, especially not knowing the details of what the COM interface is requiring. Can you elaborate if somethings is off or check for mistakes? Have tried pretty much everything I could think of, to no avail. 

 

#NoEnv
;SetFormat, Float,0.0
FormatTime, Time, YYYYMMDDHH24MISS, HH:mm:ss
FoundPos := RegExMatch(Time, "\A([0-1][0-9]|[2][0-3])\:[0-5][0-9]\:[0-5][0-9]\z", SubPat, 1)
asplit := StrSplit(SubPat, ":")
Ttime := (asplit[1] / 24) + (asplit[2] / 1440) + (asplit[3] / 86400)
;msgbox % Ttime 
FormatTime, Date, YYYYMMDDHH24MISS, yyyy/MM/dd
split := StrSplit(Date, "/")
Tdate := gojulian(split[1], split[2], split[3], 2) ; 1 = returns 5 digits and 2 = returns 7 digits
;msgbox % Tdate
Tdate -= 2415018.5 
;msgbox % Tdate
nowtime := Tdate + Ttime 
;msgbox % nowtime

iDVBViewer := ComObjActive("DVBViewerServer.DVBViewer")
channelnumber := 0 ; number of your wanted channel

epgcid := iDVBViewer.channelmanager(channelnumber).EPGChannelID
SuD := iDVBViewer.channelmanager.GetByEPGChannelID(epgcid).tuner.SID
TuD := iDVBViewer.channelmanager.GetByEPGChannelID(epgcid).tuner.TransportStreamID
epgnow := iDVBViewer.epgmanager.Get(SuD,TuD,nowtime,nowtime).item(0).Title
;epgnext := iDVBViewer.epgmanager.Get(SuD,TuD,0,0).item(1).Title
;starttime := iDVBViewer.epgmanager.Get(SuD,TuD,0,0).item(0).Time
;endtime := iDVBViewer.epgmanager.Get(SuD,TuD,0,0).item(0).Endtime
;duration := iDVBViewer.epgmanager.Get(SuD,TuD,0,0).item(0).Duration
;showinfo := iDVBViewer.epgmanager.Get(SuD,TuD,0,0).item(0).Description

msgbox % starttime . "  " . epgnow . "`n" . endtime "  " epgnext " "

;===== converts to julian date 5 digits or 7 digits ========
;===== y=year, m=month, d=day pd =1 for 5 digits, anything else 7 digits==============================
gojulian(y,m,d,pd)
{
    if (m < 3) {
        m += 12
        y--
    }
	jdn:=-678972+d+((153*m-2)//5)+365*y+(y//4)-(y//100)+(y//400)
	if (pd=1)
		return , jdn
	else
		return, jdn+2400000
}

 

Share this post


Link to post
nuts

@majstang: Maybe we can compare our results for 2017/05/05 22:50:50 ? :)

 

I get:

TTime=0.951967592592593

TDate=42860

TDateTime=42860.951967592592593

Share this post


Link to post
majstang
20 hours ago, nuts said:

@majstang: Maybe we can compare our results for 2017/05/05 22:50:50 ? :)

 

I get:

TTime=0.951967592592593

TDate=42860

TDateTime=42860.951967592592593

Hi nuts!

Hmm...this was truely some tricky shit. COM with AutoHotkey is torture and I believe its the same with AutoIt (or even worse since you stated AutoIt cant access COM object safearrays) (after this you might even consider switching to AHK;)). Are you sure your TDateTime calculation method works when retrieving now and next show info using the COM Get function with AutoIt? I think you wrote, but edited away, you were using it for adding timers and for that purpose it may work, but with Get your TDateTime value gets rejected (typematching error) by Com Get every time. I discovered this when I managed to calculate the exact same numbers you did in the quoted sample above and apply the method with system now time.

 

So I had to consult the smart guy (qwerty12) again after mere miserable failures of my own. In notime he did find this solution which works great:

 

TDateTime retrieval with AHK and COM Get now and next show info retrieval:

#NoEnv
st := SystemTime.Now()
DllCall("oleaut32\SystemTimeToVariantTime", "Ptr", st.p, "Double*", oleSt)

iDVBViewer := ComObjActive("DVBViewerServer.DVBViewer")
channelnumber := 0 ; number of your wanted channel
epgcid := iDVBViewer.channelmanager(channelnumber).EPGChannelID
SuD := iDVBViewer.channelmanager.GetByEPGChannelID(epgcid).tuner.SID
TuD := iDVBViewer.channelmanager.GetByEPGChannelID(epgcid).tuner.TransportStreamID
epgnow := iDVBViewer.epgmanager.Get(SuD,TuD,oleSt,oleSt).item(0).Title
epgnext := iDVBViewer.epgmanager.Get(SuD,TuD,oleSt,0).item(1).Title
starttime := iDVBViewer.epgmanager.Get(SuD,TuD,oleSt,oleSt).item(0).Time
endtime := iDVBViewer.epgmanager.Get(SuD,TuD,oleSt,0).item(0).Endtime
duration := iDVBViewer.epgmanager.Get(SuD,TuD,oleSt,oleSt).item(0).Duration
showinfo := iDVBViewer.epgmanager.Get(SuD,TuD,oleSt,oleSt).item(0).Description
msgbox % starttime . "  " . epgnow . "`n" . endtime "  " epgnext " "
msgbox % duration "`n" showinfo


; Written by Lexikos for EnableUIAccess
/*
    SystemTime - Wrapper for Win32 SYSTEMTIME Structure
        http://msdn.microsoft.com/en-us/library/ms724950
    Usage Examples:
    ; Create structure from string.
    st := SystemTime.FromString(A_Now)
    ; Shortcut:
    st := SystemTime.Now()
    ; Update values.
    st.FromString(A_Now)
    ; Retrieve components.
    year    := st.Year
    month   := st.Month
    weekday := st.DayOfWeek
    day     := st.Day
    hour    := st.Hour
    minute  := st.Minute
    second  := st.Second
    ms      := st.Milliseconds
    ; Set or perform math on component.
    st.Year += 10
    ; Create structure to receive output from DllCall.
    st := new SystemTime
    DllCall("GetSystemTime", "ptr", st.p)
    MsgBox % st.ToString()
    ; Fill external structure.
    st := SystemTime.FromPointer(externalPointer)
    st.FromString(A_Now)
    ; Convert external structure to string.
    MsgBox % SystemTime.ToString(externalPointer)
*/
class SystemTime
{
    FromString(str)
    {
        if this.p
            st := this
        else
            st := new this
        if !(p := st.p)
            return 0
        FormatTime wday, %str%, WDay
        wday -= 1
        FormatTime str, %str%, yyyy M '%wday%' d H m s '0'
        Loop Parse, str, %A_Space%
            NumPut(A_LoopField, p+(A_Index-1)*2, "ushort")
        return st
    }
    FromPointer(pointer)
    {
        return { p: pointer, base: this }   ; Does not call __New.
    }
    ToString(st = 0)
    {
        if !(p := (st ? (IsObject(st) ? st.p : st) : this.p))
            return ""
        VarSetCapacity(s, 28), s := SubStr("000" NumGet(p+0, "ushort"), -3)
        Loop 6
            if A_Index != 2
                s .= SubStr("0" NumGet(p+A_Index*2, "ushort"), -1)
        return s
    }
    Now()
    {
        return this.FromString(A_Now)
    }
    __New()
    {
        if !(this.SetCapacity("struct", 16))
        || !(this.p := this.GetAddress("struct"))
            return 0
        NumPut(0, NumPut(0, this.p, "int64"), "int64")
    }
    __GetSet(name, value="")
    {
        static fields := {Year:0, Month:2, DayOfWeek:4, Day:6, Hour:8
                            , Minute:10, Second:12, Milliseconds:14}
        if fields.HasKey(name)
            return value=""
                ? NumGet(       this.p + fields[name], "ushort")
                : NumPut(value, this.p + fields[name], "ushort")
    }
    static __Get := SystemTime.__GetSet
    static __Set := SystemTime.__GetSet
}

At last am I finished with the DVBViewer COM interface for a while;) Quite instructive for me and I hope I did help others as a by-product:bye:

Edited by majstang

Share this post


Link to post
nuts

Didnt try my functions with iEPGManager.Get but I can test it (behause I am not sure your dllcall always works when you need a TDateTime format). :) 

 

And yeah this COM stuff is very tricky with AHK or autoit. So its good you have something that is working.

I will post the result of my tests here when its done. 

 

 

Share this post


Link to post
majstang
8 minutes ago, nuts said:

I will post the result of my tests here when its done. 

 

Yes, please do...that will be interesting:) ...and good luck with converting the class to AutoIt:)

Share this post


Link to post
nuts
#include <date.au3>
#include <DVBViewer.au3>

Global $SID=10301 ;ARD HD
Global $TID=1019  ;ARD HD

Global $o_dvbviewer=_DVBV_Connect()
if @error then
	MsgBox(0, "Error", "DVBViewer Connect fehlgeschlagen => Exit!")
	exit
endif

Global $start=_DateTime_toTdatetime(_NowCalc())  ; Nowcalc() =>Returns the current Date and Time in format YYYY/MM/DD HH:MM:SS for use in date calculations
Global $end=_DateTime_toTdatetime(_NowCalc())    ; Nowcalc() =>Returns the current Date and Time in format YYYY/MM/DD HH:MM:SS for use in date calculations

Global $o_epgmager_GET=$o_dvbviewer.EPGManager.GET($SID, $TID, $start, $end)
Global $o_epgmager_item=$o_epgmager_GET.Item(0)

Global $s_epgtitel=$o_epgmager_item.Title
MsgBox(0, "EPG Item(0)", "Title="&$s_epgtitel)



#cs
Func _DateTime_toTdatetime($s_datetime)
	; nuts (www.autoit.de)
	;
	; $s_datetime = YYYY/MM/DD HH:MM:SS
	; return = TDateTime conform flootingpoint number (i.e.  starttime to add a timer)
	; @error = 1  => no valid date input

	Local $a_split_DateTime = StringSplit($s_datetime, " T")
	If @error = 1 Then Return SetError(1, 0, 0)
	Local $ret_date, $ret_time, $ret

	$ret_date = _Date_to_Tdate($a_split_DateTime[1])
	If @error Then Return SetError(1, 0, 0)
	$ret_time = _Time_to_Ttime($a_split_DateTime[2])
	If @error Then Return SetError(1, 0, 0)

	$ret = $ret_date + $ret_time

	Return $ret

EndFunc   ;==>_DateTime_toTdatetime
#ce

My functions are working just fine for me. :)

 

We could compare

DllCall("oleaut32\SystemTimeToVariantTime", "Ptr", st.p, "Double*", oleSt)

 

vs

 

_DateTime_toTdatetime($s_datetime)

if you want to. ;)

Share this post


Link to post
majstang
3 hours ago, nuts said:

My functions are working just fine for me. :)

Im happy it works for ya:) Although these are completely different functions from those you posted initially;)

 

3 hours ago, nuts said:

We could compare

 No, since it is working for both there is no need:bye:

Share this post


Link to post
nuts
vor 46 Minuten schrieb majstang:

Im happy it works for ya:) Although these are completely different functions from those you posted initially;)

 

 

_DateTime_toTdatetime = _Date_to_Tdate + _Time_to_Ttime (+ some string convert+ error handling)  ;) 

 

Your script is just working for "today" isnt it? Anyway if it works for you I am happy. :) 

Share this post


Link to post
majstang
1 hour ago, nuts said:

 

_DateTime_toTdatetime = _Date_to_Tdate + _Time_to_Ttime (+ some string convert+ error handling)  ;) 

 

Your script is just working for "today" isnt it? Anyway if it works for you I am happy. :) 

Aha...im really bad at reading autoit...it does look quite different from AHK, with all the dollar signs, @ and stuff:D

 

No, you can retrieve info for any day in the EPG by using the class FromString:

st := SystemTime.FromString(20170509225000)

Share this post


Link to post
nuts

Yeah should be working for "today" or any other day, but is it working for a specific time range (i.e. 2017/08/05 20:22 to 2017/08/05 23:00)?

 

Share this post


Link to post
majstang

Well, if im understanding this right you only need the TDateTime (starttime) from class the rest is up to how you arrange the COM calls, for example this works to retrieve a longer range of titles:

 

st := SystemTime.FromString(20170509185000)
;st := SystemTime.Now()
If (DllCall("oleaut32\SystemTimeToVariantTime", "Ptr", st.p, "Double*", oleSt))
  ;MsgBox % "oleSt= " oleSt "`nConverted back to intger= " DateTime_ConvertDelphiTDateTime( oleSt )


iDVBViewer := ComObjActive("DVBViewerServer.DVBViewer")
channelnumber := 1 ; number of your wanted channel
epgcid := iDVBViewer.channelmanager(channelnumber).EPGChannelID
SuD := iDVBViewer.channelmanager.GetByEPGChannelID(epgcid).tuner.SID
TuD := iDVBViewer.channelmanager.GetByEPGChannelID(epgcid).tuner.TransportStreamID
epgnow := iDVBViewer.epgmanager.Get(SuD,TuD,oleSt,oleSt).item(0).Title
epgnext := iDVBViewer.epgmanager.Get(SuD,TuD,oleSt,0).item(1).Title
epgnext2 := iDVBViewer.epgmanager.Get(SuD,TuD,oleSt,0).item(2).Title
epgnext3 := iDVBViewer.epgmanager.Get(SuD,TuD,oleSt,0).item(3).Title
epgnext4 := iDVBViewer.epgmanager.Get(SuD,TuD,oleSt,0).item(4).Title
msgbox % epgnow "`n" epgnext "`n" epgnext2 "`n" epgnext3 "`n" epgnext4

 

 

Share this post


Link to post
nuts

That's what I am trying to say. ^^

With this call:

epgnow := iDVBViewer.epgmanager.Get(SuD,TuD,oleSt,oleSt).item(0).Title

 

Imho you should just receive 1 Item (item(0)=EPGNow).

It's no problem for what you are doing here, but it can cause problems if you need a specific TDateTime  (i.e. timers etc.)

 

Edited by nuts

Share this post


Link to post
nuts

Another thing Lars told me years ago.

With this:

epgnow := iDVBViewer.epgmanager.Get(SuD,TuD,oleSt,oleSt).item(0).Title
epgnext := iDVBViewer.epgmanager.Get(SuD,TuD,oleSt,0).item(1).Title
epgnext2 := iDVBViewer.epgmanager.Get(SuD,TuD,oleSt,0).item(2).Title
epgnext3 := iDVBViewer.epgmanager.Get(SuD,TuD,oleSt,0).item(3).Title
epgnext4 := iDVBViewer.epgmanager.Get(SuD,TuD,oleSt,0).item(4).Title

You are getting the whole collection every time.

This should work better:

o_epgGet := iDVBViewer.epgmanager.Get(SuD,TuD,oleSt,0)
epgnow := o_epgGet.item(0).Title
epgnext := o_epgGet.item(1).Title

 

Share this post


Link to post
majstang
40 minutes ago, nuts said:

You are getting the whole collection every time.

This should work better:


o_epgGet := iDVBViewer.epgmanager.Get(SuD,TuD,oleSt,0)
epgnow := o_epgGet.item(0).Title
epgnext := o_epgGet.item(1).Title

 

Nice:D Wow, tested Item() with expressions and it actually responds to it, but not fully working with AHK expressions though. Hmm... I wonder how the Delphi expressions might look like. Maybe you could even set a range with those?

st := SystemTime.FromString(20170509185000)
st1 := SystemTime.FromString(20170509235000)
If (DllCall("oleaut32\SystemTimeToVariantTime", "Ptr", st.p, "Double*", oleSt))
If (DllCall("oleaut32\SystemTimeToVariantTime", "Ptr", st1.p, "Double*", range_endtime))
epgnow := iDVBViewer.epgmanager.Get(SuD,TuD,oleSt,range_endtime).item(0>1).Title
epgnow := iDVBViewer.epgmanager.Get(SuD,TuD,oleSt,range_endtime).item(0<1).Title

 

Edited by majstang

Share this post


Link to post
majstang
55 minutes ago, nuts said:

Another thing Lars told me years ago.

Yeah, "the King" is sorely missed :(

Share this post


Link to post
nuts

Of course you can set a range if your TDateTime is set up right. :) 

That's my point, because I am not sure this DLLCall gives you an accurate result. 

I will check this tomorrow. 

 

P.S. Item(0>1) seems wrong to me. 

You can get the item count and than loop through all items you want. 

 

Share this post


Link to post
nuts

@majstang I checked:

DllCall("oleaut32\SystemTimeToVariantTime", "Ptr", st.p, "Double*", oleSt))

And I am getting the same output as: 

_DateTime_toTdatetime($s_datetime)

 

Seems like I missunderstood the msdn docu for "SystemTimeToVariantTime".

Anyway both is working like it should and at least we learnded something. ;)

Share this post


Link to post

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • Create New...