diff options
author | Klaus Schmidinger <vdr@tvdr.de> | 2005-09-04 14:48:39 +0200 |
---|---|---|
committer | Klaus Schmidinger <vdr@tvdr.de> | 2005-09-04 14:48:39 +0200 |
commit | 1bc5ba817c785f6d99ab5e82a9b5f5be3238dd1c (patch) | |
tree | a323b5038b0265edfad42422a8d8534895bc1496 | |
parent | c4ff05bbcaf8dd31ac88bdc76f2815da25ac08b0 (diff) | |
download | vdr-1bc5ba817c785f6d99ab5e82a9b5f5be3238dd1c.tar.gz vdr-1bc5ba817c785f6d99ab5e82a9b5f5be3238dd1c.tar.bz2 |
Changed the audio PID language codes to hold up to two 3 letter codes; automatic audio channel setting for two channel audio
-rw-r--r-- | CONTRIBUTORS | 2 | ||||
-rw-r--r-- | HISTORY | 7 | ||||
-rw-r--r-- | channels.c | 25 | ||||
-rw-r--r-- | channels.h | 13 | ||||
-rw-r--r-- | config.c | 3 | ||||
-rw-r--r-- | config.h | 3 | ||||
-rw-r--r-- | device.c | 11 | ||||
-rw-r--r-- | device.h | 10 | ||||
-rw-r--r-- | eit.c | 6 | ||||
-rw-r--r-- | eitscan.h | 4 | ||||
-rw-r--r-- | i18n.c | 31 | ||||
-rw-r--r-- | i18n.h | 9 | ||||
-rw-r--r-- | pat.c | 29 | ||||
-rw-r--r-- | timers.c | 3 | ||||
-rw-r--r-- | vdr.5 | 7 |
15 files changed, 111 insertions, 52 deletions
diff --git a/CONTRIBUTORS b/CONTRIBUTORS index c5c7fe72..c6efd0c7 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -1296,6 +1296,8 @@ Uwe Hanke <uhanke@gmx.de> Mogens Elneff <mogens@elneff.dk> for translating OSD texts to the Danish language + for his help in testing automatically selecting the proper audio channel when + switching to a channel with two different languages on the same PID Joachim Wilke <vdr@joachim-wilke.de> for reporting missing calls to cStatus::MsgOsdClear() in cSkins::Message() @@ -3747,7 +3747,7 @@ Video Disk Recorder Revision History - The new SVDRP command EDIT can be used to start the editing process of a recording (based on the CUTR patch by Harald Milz). -2005-09-03: Version 1.3.32 +2005-09-04: Version 1.3.32 - Added some missing braces in remux.c (thanks to Wayne Keer for reporting this one). - Removed unused MAINMENUENTRY from svdrpdemo.c (thanks to Udo Richter for reporting @@ -3792,3 +3792,8 @@ Video Disk Recorder Revision History - Implemented the SVDRP command MOVC (thanks to Andreas Brachold). - Added support for multiple audio language codes in ISO639LanguageDescriptors to 'libsi' (thanks to Marcel Wiesweg). +- Changed the audio PID language codes to hold up to two 3 letter codes, separated + by '+', to store separate languages broadcast in two channel audio mode. +- If the preferred audio language is broadcast on a PID that has two different + languages in the two stereo channels, the audio channel is now properly set when + switching to such a channel (thanks to Mogens Elneff for his help in testing this). @@ -4,12 +4,13 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: channels.c 1.44 2005/08/06 12:22:41 kls Exp $ + * $Id: channels.c 1.45 2005/09/04 10:49:12 kls Exp $ */ #include "channels.h" #include <linux/dvb/frontend.h> #include <ctype.h> +#include "device.h" // IMPORTANT NOTE: in the 'sscanf()' calls there is a blank after the '%d' // format characters in order to allow any number of blanks after a numeric @@ -386,7 +387,7 @@ void cChannel::SetPortalName(const char *PortalName) #define STRDIFF 0x01 #define VALDIFF 0x02 -static int IntArraysDiffer(const int *a, const int *b, const char na[][4] = NULL, const char nb[][4] = NULL) +static int IntArraysDiffer(const int *a, const int *b, const char na[][MAXLANGCODE2] = NULL, const char nb[][MAXLANGCODE2] = NULL) { int result = 0; for (int i = 0; a[i] || b[i]; i++) { @@ -400,7 +401,7 @@ static int IntArraysDiffer(const int *a, const int *b, const char na[][4] = NULL return result; } -static int IntArrayToString(char *s, const int *a, int Base = 10, const char n[][4] = NULL) +static int IntArrayToString(char *s, const int *a, int Base = 10, const char n[][MAXLANGCODE2] = NULL) { char *q = s; int i = 0; @@ -416,7 +417,7 @@ static int IntArrayToString(char *s, const int *a, int Base = 10, const char n[] return q - s; } -void cChannel::SetPids(int Vpid, int Ppid, int *Apids, char ALangs[][4], int *Dpids, char DLangs[][4], int Tpid) +void cChannel::SetPids(int Vpid, int Ppid, int *Apids, char ALangs[][MAXLANGCODE2], int *Dpids, char DLangs[][MAXLANGCODE2], int Tpid) { int mod = CHANNELMOD_NONE; if (vpid != Vpid || ppid != Ppid || tpid != Tpid) @@ -427,8 +428,9 @@ void cChannel::SetPids(int Vpid, int Ppid, int *Apids, char ALangs[][4], int *Dp if (m & VALDIFF) mod |= CHANNELMOD_PIDS; if (mod) { - char OldApidsBuf[(MAXAPIDS + MAXDPIDS) * 10 + 10]; // 10: 5 digits plus delimiting ',' or ';' plus optional '=cod', +10: paranoia - char NewApidsBuf[(MAXAPIDS + MAXDPIDS) * 10 + 10]; + const int BufferSize = (MAXAPIDS + MAXDPIDS) * (5 + 1 + MAXLANGCODE2) + 10; // 5 digits plus delimiting ',' or ';' plus optional '=cod+cod', +10: paranoia + char OldApidsBuf[BufferSize]; + char NewApidsBuf[BufferSize]; char *q = OldApidsBuf; q += IntArrayToString(q, apids, 10, alangs); if (dpids[0]) { @@ -448,12 +450,12 @@ void cChannel::SetPids(int Vpid, int Ppid, int *Apids, char ALangs[][4], int *Dp ppid = Ppid; for (int i = 0; i < MAXAPIDS; i++) { apids[i] = Apids[i]; - strn0cpy(alangs[i], ALangs[i], 4); + strn0cpy(alangs[i], ALangs[i], MAXLANGCODE2); } apids[MAXAPIDS] = 0; for (int i = 0; i < MAXDPIDS; i++) { dpids[i] = Dpids[i]; - strn0cpy(dlangs[i], DLangs[i], 4); + strn0cpy(dlangs[i], DLangs[i], MAXLANGCODE2); } dpids[MAXDPIDS] = 0; tpid = Tpid; @@ -633,7 +635,8 @@ cString cChannel::ToText(const cChannel *Channel) if (Channel->ppid && Channel->ppid != Channel->vpid) q += snprintf(q, sizeof(vpidbuf) - (q - vpidbuf), "+%d", Channel->ppid); *q = 0; - char apidbuf[(MAXAPIDS + MAXDPIDS) * 10 + 10]; // 10: 5 digits plus delimiting ',' or ';' plus optional '=cod', +10: paranoia + const int BufferSize = (MAXAPIDS + MAXDPIDS) * (5 + 1 + MAXLANGCODE2) + 10; // 5 digits plus delimiting ',' or ';' plus optional '=cod+cod', +10: paranoia + char apidbuf[BufferSize]; q = apidbuf; q += IntArrayToString(q, Channel->apids, 10, Channel->alangs); if (Channel->dpids[0]) { @@ -722,7 +725,7 @@ bool cChannel::Parse(const char *s) char *l = strchr(q, '='); if (l) { *l++ = 0; - strn0cpy(alangs[NumApids], l, 4); + strn0cpy(alangs[NumApids], l, MAXLANGCODE2); } else *alangs[NumApids] = 0; @@ -743,7 +746,7 @@ bool cChannel::Parse(const char *s) char *l = strchr(q, '='); if (l) { *l++ = 0; - strn0cpy(dlangs[NumDpids], l, 4); + strn0cpy(dlangs[NumDpids], l, MAXLANGCODE2); } else *dlangs[NumDpids] = 0; @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: channels.h 1.33 2005/08/06 11:23:32 kls Exp $ + * $Id: channels.h 1.34 2005/09/04 10:17:12 kls Exp $ */ #ifndef __CHANNELS_H @@ -36,6 +36,9 @@ #define MAXSPIDS 8 // subtitles #define MAXCAIDS 8 // conditional access +#define MAXLANGCODE1 4 // a 3 letter language code, zero terminated +#define MAXLANGCODE2 8 // up to two 3 letter language codes, separated by '+' and zero terminated + struct tChannelParameterMap { int userValue; int driverValue; @@ -102,11 +105,11 @@ private: int vpid; int ppid; int apids[MAXAPIDS + 1]; // list is zero-terminated - char alangs[MAXAPIDS][4]; + char alangs[MAXAPIDS][MAXLANGCODE2]; int dpids[MAXDPIDS + 1]; // list is zero-terminated - char dlangs[MAXDPIDS][4]; + char dlangs[MAXDPIDS][MAXLANGCODE2]; int spids[MAXSPIDS + 1]; // list is zero-terminated - char slangs[MAXSPIDS][4]; + char slangs[MAXSPIDS][MAXLANGCODE2]; int tpid; int caids[MAXCAIDS + 1]; // list is zero-terminated int nid; @@ -188,7 +191,7 @@ public: void SetId(int Nid, int Tid, int Sid, int Rid = 0); void SetName(const char *Name, const char *ShortName, const char *Provider); void SetPortalName(const char *PortalName); - void SetPids(int Vpid, int Ppid, int *Apids, char ALangs[][4], int *Dpids, char DLangs[][4], int Tpid); + void SetPids(int Vpid, int Ppid, int *Apids, char ALangs[][MAXLANGCODE2], int *Dpids, char DLangs[][MAXLANGCODE2], int Tpid); void SetCaIds(const int *CaIds); // list must be zero-terminated void SetCaDescriptors(int Level); void SetLinkChannels(cLinkChannels *LinkChannels); @@ -4,12 +4,13 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: config.c 1.136 2005/08/13 13:47:08 kls Exp $ + * $Id: config.c 1.137 2005/09/04 10:49:38 kls Exp $ */ #include "config.h" #include <ctype.h> #include <stdlib.h> +#include "device.h" #include "i18n.h" #include "interface.h" #include "plugin.h" @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: config.h 1.226 2005/08/28 21:19:26 kls Exp $ + * $Id: config.h 1.227 2005/09/04 10:49:24 kls Exp $ */ #ifndef __CONFIG_H @@ -16,7 +16,6 @@ #include <string.h> #include <time.h> #include <unistd.h> -#include "device.h" #include "i18n.h" #include "tools.h" @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: device.c 1.108 2005/09/02 13:52:31 kls Exp $ + * $Id: device.c 1.109 2005/09/04 14:28:16 kls Exp $ */ #include "device.h" @@ -795,13 +795,17 @@ void cDevice::EnsureAudioTrack(bool Force) { if (Force || !availableTracks[currentAudioTrack].id) { eTrackType PreferredTrack = ttAudioFirst; + int PreferredAudioChannel = 0; int LanguagePreference = -1; int StartCheck = Setup.CurrentDolby ? ttDolbyFirst : ttAudioFirst; int EndCheck = ttDolbyLast; for (int i = StartCheck; i <= EndCheck; i++) { const tTrackId *TrackId = GetTrack(eTrackType(i)); - if (TrackId && TrackId->id && I18nIsPreferredLanguage(Setup.AudioLanguages, I18nLanguageIndex(TrackId->language), LanguagePreference)) + int pos = 0; + if (TrackId && TrackId->id && I18nIsPreferredLanguage(Setup.AudioLanguages, TrackId->language, LanguagePreference, &pos)) { PreferredTrack = eTrackType(i); + PreferredAudioChannel = pos; + } if (Setup.CurrentDolby && i == ttDolbyLast) { i = ttAudioFirst - 1; EndCheck = ttAudioLast; @@ -811,8 +815,9 @@ void cDevice::EnsureAudioTrack(bool Force) const tTrackId *Track = GetTrack(GetCurrentAudioTrack()); if (Force || !Track || !Track->id || PreferredTrack != GetCurrentAudioTrack()) { if (!Force) // only log this for automatic changes - dsyslog("setting audio track to %d", PreferredTrack); + dsyslog("setting audio track to %d (%d)", PreferredTrack, PreferredAudioChannel); SetCurrentAudioTrack(PreferredTrack); + SetAudioChannel(PreferredAudioChannel); } } } @@ -4,12 +4,13 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: device.h 1.62 2005/08/21 08:52:20 kls Exp $ + * $Id: device.h 1.63 2005/09/04 10:50:36 kls Exp $ */ #ifndef __DEVICE_H #define __DEVICE_H +#include "channels.h" #include "ci.h" #include "eit.h" #include "filter.h" @@ -81,12 +82,11 @@ enum eTrackType { ttNone, #define IS_DOLBY_TRACK(t) (ttDolbyFirst <= (t) && (t) <= ttDolbyLast) struct tTrackId { - uint16_t id; // The PES packet id or the PID. - char language[8]; // something like either "eng" or "deu/eng" - char description[32]; // something like "Dolby Digital 5.1" + uint16_t id; // The PES packet id or the PID. + char language[MAXLANGCODE2]; // something like either "eng" or "deu+eng" + char description[32]; // something like "Dolby Digital 5.1" }; -class cChannel; class cPlayer; class cReceiver; class cPesAssembler; @@ -8,7 +8,7 @@ * Robert Schneider <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>. * Adapted to 'libsi' for VDR 1.3.0 by Marcel Wiesweg <marcel.wiesweg@gmx.de>. * - * $Id: eit.c 1.110 2005/08/13 13:27:34 kls Exp $ + * $Id: eit.c 1.111 2005/09/04 11:36:30 kls Exp $ */ #include "eit.h" @@ -99,7 +99,7 @@ cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data) switch (d->getDescriptorTag()) { case SI::ExtendedEventDescriptorTag: { SI::ExtendedEventDescriptor *eed = (SI::ExtendedEventDescriptor *)d; - if (I18nIsPreferredLanguage(Setup.EPGLanguages, I18nLanguageIndex(eed->languageCode), LanguagePreferenceExt) || !ExtendedEventDescriptors) { + if (I18nIsPreferredLanguage(Setup.EPGLanguages, eed->languageCode, LanguagePreferenceExt) || !ExtendedEventDescriptors) { delete ExtendedEventDescriptors; ExtendedEventDescriptors = new SI::ExtendedEventDescriptors; UseExtendedEventDescriptor = true; @@ -114,7 +114,7 @@ cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data) break; case SI::ShortEventDescriptorTag: { SI::ShortEventDescriptor *sed = (SI::ShortEventDescriptor *)d; - if (I18nIsPreferredLanguage(Setup.EPGLanguages, I18nLanguageIndex(sed->languageCode), LanguagePreferenceShort) || !ShortEventDescriptor) { + if (I18nIsPreferredLanguage(Setup.EPGLanguages, sed->languageCode, LanguagePreferenceShort) || !ShortEventDescriptor) { delete ShortEventDescriptor; ShortEventDescriptor = sed; d = NULL; // so that it is not deleted @@ -4,14 +4,16 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: eitscan.h 1.8 2004/01/17 15:36:24 kls Exp $ + * $Id: eitscan.h 1.9 2005/09/04 10:51:35 kls Exp $ */ #ifndef __EITSCAN_H #define __EITSCAN_H #include <time.h> +#include "channels.h" #include "config.h" +#include "device.h" class cScanList; class cTransponderList; @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: i18n.c 1.204 2005/09/02 12:44:09 kls Exp $ + * $Id: i18n.c 1.205 2005/09/04 14:28:07 kls Exp $ * * Translations provided by: * @@ -5500,7 +5500,7 @@ const char * const * I18nCharSets(void) return &Phrases[1][0]; } -const char * I18nLanguageCode(int Index) +const char *I18nLanguageCode(int Index) { return 0 <= Index && Index < I18nNumLanguages ? Phrases[2][Index] : NULL; } @@ -5508,8 +5508,17 @@ const char * I18nLanguageCode(int Index) int I18nLanguageIndex(const char *Code) { for (int i = 0; i < I18nNumLanguages; i++) { - if (strcasestr(Phrases[2][i], Code)) - return i; + const char *s = Phrases[2][i]; + while (*s) { + int l = 0; + for ( ; l < 3 && Code[l]; l++) { + if (s[l] != tolower(Code[l])) + break; + } + if (l == 3) + return i; + s++; + } } //dsyslog("unknown language code: '%s'", Code); return -1; @@ -5527,19 +5536,31 @@ const char *I18nNormalizeLanguageCode(const char *Code) return n >= 0 ? I18nLanguageCode(n) : Code; } -bool I18nIsPreferredLanguage(int *PreferredLanguages, int LanguageIndex, int &OldPreference) +bool I18nIsPreferredLanguage(int *PreferredLanguages, const char *LanguageCode, int &OldPreference, int *Position) { + if (Position) + *Position = 0; +Retry: + int LanguageIndex = I18nLanguageIndex(LanguageCode); for (int i = 0; i < I18nNumLanguages; i++) { if (PreferredLanguages[i] < 0) break; // the language is not a preferred one if (PreferredLanguages[i] == LanguageIndex) { if (OldPreference < 0 || i < OldPreference) { OldPreference = i; + if (Position && !*Position && strchr(LanguageCode, '+')) + (*Position)++; return true; } break; } } + if ((LanguageCode = strchr(LanguageCode, '+')) != NULL) { + LanguageCode++; + if (Position) + (*Position)++; + goto Retry; + } if (OldPreference < 0) { OldPreference = I18nNumLanguages; // higher than the maximum possible value return true; // if we don't find a preferred one, we take the first one @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: i18n.h 1.14 2004/11/06 10:26:52 kls Exp $ + * $Id: i18n.h 1.15 2005/09/04 14:37:01 kls Exp $ */ #ifndef __I18N_H @@ -22,10 +22,13 @@ const char *I18nTranslate(const char *s, const char *Plugin = NULL); const char * const * I18nLanguages(void); const char * const * I18nCharSets(void); -const char * I18nLanguageCode(int Index); +const char *I18nLanguageCode(int Index); int I18nLanguageIndex(const char *Code); const char *I18nNormalizeLanguageCode(const char *Code); -bool I18nIsPreferredLanguage(int *PreferredLanguages, int LanguageIndex, int &OldPreference); + ///< Returns a 3 letter language code that may not be zero terminated. + ///< If no normalized language code can be found, the given Code is returned. + ///< Make sure at most 3 characters are copied when using it! +bool I18nIsPreferredLanguage(int *PreferredLanguages, const char *LanguageCode, int &OldPreference, int *Position = NULL); #ifdef PLUGIN_NAME_I18N #define tr(s) I18nTranslate(s, PLUGIN_NAME_I18N) @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: pat.c 1.13 2005/08/06 12:23:51 kls Exp $ + * $Id: pat.c 1.14 2005/09/04 14:32:39 kls Exp $ */ #include "pat.h" @@ -326,8 +326,8 @@ void cPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length int Ppid = pmt.getPCRPid(); int Apids[MAXAPIDS + 1] = { 0 }; // these lists are zero-terminated int Dpids[MAXDPIDS + 1] = { 0 }; - char ALangs[MAXAPIDS][4] = { "" }; - char DLangs[MAXDPIDS][4] = { "" }; + char ALangs[MAXAPIDS][MAXLANGCODE2] = { "" }; + char DLangs[MAXDPIDS][MAXLANGCODE2] = { "" }; int Tpid = 0; int NumApids = 0; int NumDpids = 0; @@ -347,10 +347,19 @@ void cPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length switch (d->getDescriptorTag()) { case SI::ISO639LanguageDescriptorTag: { SI::ISO639LanguageDescriptor *ld = (SI::ISO639LanguageDescriptor *)d; - if (*ld->languageCode != '-') { // some use "---" to indicate "none" - strn0cpy(ALangs[NumApids], I18nNormalizeLanguageCode(ld->languageCode), 4); - ALangs[NumApids][4] = 0; - } + SI::ISO639LanguageDescriptor::Language l; + char *s = ALangs[NumApids]; + int n = 0; + for (SI::Loop::Iterator it; ld->languageLoop.getNext(l, it); ) { + if (*ld->languageCode != '-') { // some use "---" to indicate "none" + if (n > 0) + *s++ = '+'; + strn0cpy(s, I18nNormalizeLanguageCode(l.languageCode), MAXLANGCODE1); + s += strlen(s); + if (n++ > 1) + break; + } + } } break; default: ; @@ -366,7 +375,7 @@ void cPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length //XXX case 8: // STREAMTYPE_13818_DSMCC { int dpid = 0; - char lang[4] = { 0 }; + char lang[MAXLANGCODE1] = { 0 }; SI::Descriptor *d; for (SI::Loop::Iterator it; (d = stream.streamDescriptors.getNext(it)); ) { switch (d->getDescriptorTag()) { @@ -378,7 +387,7 @@ void cPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length break; case SI::ISO639LanguageDescriptorTag: { SI::ISO639LanguageDescriptor *ld = (SI::ISO639LanguageDescriptor *)d; - strn0cpy(lang, I18nNormalizeLanguageCode(ld->languageCode), 4); + strn0cpy(lang, I18nNormalizeLanguageCode(ld->languageCode), MAXLANGCODE1); } break; default: ; @@ -388,7 +397,7 @@ void cPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length if (dpid) { if (NumDpids < MAXDPIDS) { Dpids[NumDpids] = dpid; - strn0cpy(DLangs[NumDpids], lang, 4); + strn0cpy(DLangs[NumDpids], lang, MAXLANGCODE1); NumDpids++; } } @@ -4,12 +4,13 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: timers.c 1.34 2005/07/30 13:03:51 kls Exp $ + * $Id: timers.c 1.35 2005/09/04 10:53:21 kls Exp $ */ #include "timers.h" #include <ctype.h> #include "channels.h" +#include "device.h" #include "i18n.h" #include "remote.h" @@ -8,7 +8,7 @@ .\" License as specified in the file COPYING that comes with the .\" vdr distribution. .\" -.\" $Id: vdr.5 1.37 2005/05/16 14:16:48 kls Exp $ +.\" $Id: vdr.5 1.38 2005/09/04 14:43:42 kls Exp $ .\" .TH vdr 5 "19 Mar 2005" "1.3.23" "Video Disk Recorder Files" .SH NAME @@ -136,6 +136,11 @@ by an '=' sign, as in .B ...:101=deu,102=eng;103=deu,104=eng:... +Some channels broadcast two different languages in the two stereo channels, which +can be indicated by adding a second language code, delimited by a '+' sign, as in + +.B ...:101=deu,102=eng+spa;103=deu,104=eng:... + .TP .B TPID The teletext PID. |