<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="metadata.album.universal"
name="Universal Album Scraper"
- version="1.6.2"
+ version="1.6.4"
provider-name="Olympia, Team XBMC">
<requires>
<import addon="xbmc.metadata" version="1.0"/>
language="en"
library="albumuniversal.xml"/>
<extension point="xbmc.addon.metadata">
+ <summary lang="af">Universele Skraper vir Albums</summary>
<summary lang="be">Universal Scraper for Albums</summary>
<summary lang="bg">Универсален източник за информация за албуми</summary>
<summary lang="ca">Arreplegador universal per àlbums</summary>
<summary lang="cs">Univerzální zdroj získávání dat pro alba</summary>
- <summary lang="da">Universel scraper til albummer</summary>
+ <summary lang="da">Universel Scraper til Albummer</summary>
<summary lang="de">Universeller Scraper für Alben</summary>
<summary lang="el">Universal Scraper Άλμπουμ</summary>
<summary lang="en">Universal Scraper for Albums</summary>
<summary lang="es">Scraper universal para álbumes</summary>
<summary lang="es_AR">Scraper Universal para Albumes</summary>
<summary lang="es_MX">Scraper Universal para Álbumes</summary>
+ <summary lang="fi">Universaali tiedonhakija albumitiedoille</summary>
<summary lang="fr">Scraper universel pour les Albums</summary>
<summary lang="gl">Scraper universal para os álbumes</summary>
<summary lang="he">סקרייפר אוניברסלי עבור אלבומים</summary>
<summary lang="hu">Univerzális album leolvasó</summary>
<summary lang="it">Ricercatore Universale per Albums</summary>
+ <summary lang="ja">汎用アルバムスクレーパー</summary>
<summary lang="ko">Universal Scraper for Albums</summary>
<summary lang="lt">Universalus Albumų Scrapers</summary>
<summary lang="mk">Универзален превземач за Албуми</summary>
<summary lang="pl">Universal Scraper dla albumów muzycznych</summary>
<summary lang="pt">Scraper universal para álbuns</summary>
<summary lang="pt_BR">Scraper Universal para Álbuns</summary>
+ <summary lang="ro"> Scraper universal pentru albume</summary>
<summary lang="ru">Универсальный скрапер для альбомов</summary>
<summary lang="sk">Univerzálny zdroj získavania dát pre albumy</summary>
<summary lang="sl">Univerzalni ponudnik informacij o albumih</summary>
<summary lang="sv">Universiell albumskrapa</summary>
<summary lang="zh">通用音乐专辑信息刮削器</summary>
+ <description lang="af">Hierdie skraper kollekteer inligting vanaf die volgende ondersteunde werwe: MusicBrainz, last.fm, allmusic.com en amazon.de, terwyl kunswerke gegryp word vanaf: fanart.tv, last.fm en allmusic.com. Dit kan veld vir veld gestel word vanaf watter werf jy die spesifieke inligting wil hê. Die aanvaklike soektog word altyd op MusicBrainz gedoen. Ingeval skakels van allmusic en/of amazon.de nie bygevoeg is op die MusicBrainz werf, kan velde vanaf allmusic.com en/of amazon.de nie gaan haal word nie (egter baie maklik om daardie ontbrekende skakels by te voeg).</description>
<description lang="be">This scraper collects information from the following supported sites: MusicBrainz, last.fm, allmusic.com and amazon.de, while grabs artwork from: fanart.tv, last.fm and allmusic.com. It can be set field by field that from which site you want that specific information. The initial search is always done on MusicBrainz. In case allmusic and/or amazon.de links are not added on the MusicBrainz site, fields from allmusic.com and/or amazon.de cannot be fetched (very easy to add those missing links though).</description>
<description lang="bg">Сваля информация от следните сайтове: MusicBrainz, last.fm, allmusic.com и amazon.de. Изобрания извлича от: fanart.tv, last.fm и allmusic.com. Можете да определите от кой сайт каква информация да се сваля. Първоначалното търсене винаги се прави в MusicBrainz. Когато не са налични препратки към allmusic и/или amazon.de на сайта на MusicBrainz извличането на информация от allmusic.com и/или amazon.de става невъзможно (добавянето им е много лесно).</description>
<description lang="ca">Aquest arreplegador recull informació dels següents llocs: MusicBrainz, last.fm, allmusic.com i amazon.de, alhora que agafa art de: fanart.tv, last.fm i allmusic.com. Es pot configurar camp a camp des de quin lloc voleu aquella informació específica. La cerca inicial es realitza sempre a MusicBrainz. En el cas que els enllaços d'allmusic i amazon.de no s'afegeixin al lloc MusicBrainz els camps d'allmusic i amazon.de i/o amazon.com no es poden obtenir (tot i que són fàcils d'afegir aquests enllaços restants).</description>
<description lang="cs">Tento zdroj získává data z následujících podporovaných stránek: MusicBrainz, last.fm, allmusic.com and amazon.de, while grabs artwork from: fanart.tv, last.fm a allmusic.com. Je možno podrobně nastavit, které informace si přejete získat ze kterých stránek. Prvotní vyhledání je vžy provedeno na MusicBrainz. V případě, že odkaz pro allmusic a nebo amazon.de není přidaný na stránku MusicBrainz, není možné údaje z allmusic.com a amazon.de stáhnout (avšak je velmi snadné tyto chybějící odkazy přidat).</description>
- <description lang="da">Denne scraper samler information fra følgende sider: MusicBrainz, last.fm, allmusic.com og amazon.de, og henter illustrationer fra: fanart.tv, last.fm og allmusic.com. Den kan sættes felt for felt fra de sider du nu måtte ønske at få de pågældende informationer fra. Den første søgning er altid på MusicBrainz. Hvis ikke allmusic og/eller amazon.de linkene er tilføjet på MusicBrainz side, kan felter fra allmusic.com og/eller amazon.de ikke hentes (men det er helt enkelt at tilføje de manglende links).</description>
+ <description lang="da">Denne scraper samler information fra følgende sider: MusicBrainz, last.fm, allmusic.com og amazon.de og henter illustrationer fra: fanart.tv, last.fm og allmusic.com. Det kan vælges felt for felt, hvilke sider du ønsker, at få den pågældende information fra. Den første søgning er altid på MusicBrainz. Hvis ikke allmusic og/eller amazon.de linkene er tilføjet på MusicBrainz side, kan felter fra allmusic.com og/eller amazon.de ikke hentes (men det er helt enkelt at tilføje de manglende links).</description>
<description lang="de">Dieser Scaper sammelt Informationen von den folgenden unterstützten Seiten: MusicBrainz, Last.fm, Allmusic.com und Amazon.de und lädt dabei Grafiken von: Fanart.tv, Last.fm und Allmusic.com. Es kann eingestellt werden, von welcher Seite die spezifischen Informationen stammen.⏎ ⏎ Die initialie Suche wird immer über MusicBrainz durchgeführt. Sollten keine Allmusic- und/oder Amazon.de-Links auf der MusicBrainz-Seite gesetzt worden sein, können Felder von Allmusic.com und/oder Amazon.de nicht geladen werden (fehlende Links können jedoch sehr einfach hinzugefügt werden).</description>
<description lang="el">Αυτό το scraper συλλέγει πληροφορίες από τις ακόλουθες υποστηριζόμενες ιστοσελίδες: MusicBrainz, last.fm, allmusic.com και amazon.de, ενώ λαμβάνει εικόνες από τα: fanart.tv, last.fm και allmusic.com. Μπορεί να οριστεί πεδίο ανά πεδίο αναλόγως των πληροφοριών που επιθυμείτε από την εκάστοτε ιστοσελίδα. Η αρχική αναζήτηση γίνεται πάντα στο MusicBrainz. Σε περίπτωση που δεν έχουν προστεθεί σύνδεσμοι για το allmusic ή/και το amazon.de στο MusicBrainz, δε θα μπορούν να ληφθούν πεδία από το allmusic.com ή/και το amazon.de (ωστόσο είναι πολύ εύκολο να προστεθούν αυτοί οι ελλιπείς σύνδεσμοι).</description>
<description lang="en">This scraper collects information from the following supported sites: MusicBrainz, last.fm, allmusic.com and amazon.de, while grabs artwork from: fanart.tv, last.fm and allmusic.com. It can be set field by field that from which site you want that specific information. The initial search is always done on MusicBrainz. In case allmusic and/or amazon.de links are not added on the MusicBrainz site, fields from allmusic.com and/or amazon.de cannot be fetched (very easy to add those missing links though).</description>
<description lang="es">Este scraper recolecta información de las siguientes webs: MusicBrainz, last.fm, allmusic.com y amazon.de, mientras obtiene las imágenes de fanart.tv, last.fm y allmusic.com. Permite especificar de qué web se quiere obtener la información para cada campo. ⏎ ⏎ La búsqueda inicial se realiza siempre en MusicBrainz. En caso de que los enlaces a allmusic y/o amazon.de no estén añadidos en la web de MusicBrainz, no se podrán obtener los campos de información de estos dos sitios (aunque es muy fácil añadir esos enlaces que faltan).</description>
<description lang="es_AR">Este scraper recolecta información de las siguientes webs: MusicBrainz, last.fm, allmusic.com y amazon.de, mientras obtiene las imágenes de fanart.tv, last.fm y allmusic.com. Permite especificar de qué web se quiere obtener la información para cada campo. ⏎ ⏎ La búsqueda inicial se realiza siempre en MusicBrainz. En caso de que los enlaces a allmusic y/o amazon.de no estén añadidos en la web de MusicBrainz, no se podrán obtener los campos de información de estos dos sitios (aunque es muy fácil añadir esos enlaces que faltan).</description>
<description lang="es_MX">Este scraper obtiene información de los siguientes sitios soportados: MusicBrainz, last.fm, allmusic.com y amazon.de, mientras obtiene artwork de: fanart.tv, last.fm y allmusic.com. Puede ser configurado campo por campo de que sitio quieres esa información específica.⏎ ⏎ La búsqueda inicial siempre es hecha en MusicBrainz. En el caso en que los enlaces de allmusic y/o amazon.de no son agregados en el sitio de MusicBrainz, campos de allmusic.com y/o amazon.de no pueden ser obtenidos. (De cualquier manera es muy fácil agregar esos enlaces no encontrados).</description>
+ <description lang="fi">Tämä tiedonhakija käyttää tietolähteinään seuraavia sivustoja:MusicBrainz, last.fm, allmusic.com ja amazon.de. Kuvat ja grafiikat haetaan seuraavilta sivustoilta: fanart.tv, last.fm ja allmusic.com. Voit määrittää tarkasti, miltä sivustoilta mitäkin tietoa haetaan. Ensimmäinen haku tehdään aina lähteestä MusicBrainz. Mikäli allmusic- ja/tai amazon.de-linkkejä ei ole lisätty MusicBrainz-sivustolle, haku ei onnistu (mutta puuttuvien linkkien lisääminen on helppoa).</description>
<description lang="fr">Ce collecteur utilise les informations des sites suivant : MusicBrainz, last.fm, allmusic.com et amazon.de et télécharge les artwork depuis : fanart.tv, last.fm et allmusic.com. Vous pouvez spécifier quel site utiliser pour chaque information à employer. Le premier résultat est toujours en provenance de MusicBrainz. Dans le cas où allmusic et/ou amazon.de ne sont pas ajoutés sur le site MusicBrainz, les champs de allmusic.com et/ou amazon.de ne peuvent pas être traités (il est toutefois très simple d'ajouter ces liens manquants).</description>
<description lang="gl">Este scraper obtén a información dende os seguintes sitios soportados: MusicBrainz, last.fm, allmusic.com e amazon.de, mentres que as ilustracións as obtén dende: fanart.tv, last.fm e allmusic.com. Pódese definir dende que sitios se obterá a información específica de cada campo. A busca inicial faise sempre en MusicBrainz. No caso de non estar engadidas as ligazón de allmusic e/ou amazon.de no sitio de MusicBrainz, os campos de allmusic.com e/ou amazon.de non se poderán obter (aínda que é moi doado de engadir as ligazóns).</description>
- <description lang="he">This scraper collects information from the following supported sites: MusicBrainz, last.fm, allmusic.com and amazon.de, while grabs artwork from: fanart.tv, last.fm and allmusic.com. It can be set field by field that from which site you want that specific information. The initial search is always done on MusicBrainz. In case allmusic and/or amazon.de links are not added on the MusicBrainz site, fields from allmusic.com and/or amazon.de cannot be fetched (very easy to add those missing links though).</description>
+ <description lang="he">סקרייפר זה אוסף מידע מהתארים הנתמכים הבאים: TheAudioDb.com, MusicBrainz, last.fm, and allmusic.com, בעוד שמשיכת גרפיקה נעשית דרך: fanart.tv, htbackdrops.com, last.fm and allmusic.com. יש אפשרות להגדיר את זאת שדה אחר שדה מאיזה אתר ברצונך למשוך את המידע הספציפי.⏎ ⏎ החיפוש הפנימי נעשה תמיד דרך MusicBrainz. במקרה שקישור allmusic לא התווסף באתר MusicBrainz, השדות מאת allmusic.com אינן יכולות להימשך (למרות שזה מאוד קל להוסיף את הקישורים הריקים).</description>
<description lang="hu">A leolvasó szöveges infókat gyűjt a támogatott oldalakról: MusicBrainz, last.fm, allmusic.com and amazon.de, valamint képeket a : fanart.tv, last.fm and allmusic.com-ról. Beállítható hogy honnan, és milyen infót gyűjtsön. A keresés mindig a MusicBrainz-on folyik, de könnyen hozzáadhatjuk a hiányzó találatokat az allmusic.com, vagy az amazon.de-ről.</description>
<description lang="it">Questo ricercatore prende informazioni dai seguenti siti: MusicBrainz, last.fm, allmusic.com e amazon.de, mentre prende grafica da: fanart.tv, last.fm e allmusic.com. Puoi impostare campo per campo cosa prendere e da dove.⏎ ⏎La ricerca iniziale viene sempre fatta su MusicBrainz. In caso i links a allmusic e/o amazon.de non sono stati messi su MusicBrainz, i campi da questi siti non possono essere recuperati (è molto facile aggiungere quei link, comunque).</description>
+ <description lang="ja">このスクレーパーは、以下のサイトから情報を取得します:MusicBrainz, last.fm, allmusic.com, amazon.de。アートワークは以下のサイトから取得します:fanart.tv, last.fm, allmusic.com。各フィールドごとに、どの情報をどのサイトから取得するか、個別に指定することもできます。 最初は MusicBrainz を使って検索が行われます。MusicBrainz サイトに allmusic や amazon.de のリンクが追加されていない場合は、allmusic.com / amazon.de からのフィールドは取得できません (リンクを追加するのは簡単ですが)。</description>
<description lang="ko">This scraper collects information from the following supported sites: MusicBrainz, last.fm, allmusic.com and amazon.de, while grabs artwork from: fanart.tv, last.fm and allmusic.com. It can be set field by field that from which site you want that specific information. The initial search is always done on MusicBrainz. In case allmusic and/or amazon.de links are not added on the MusicBrainz site, fields from allmusic.com and/or amazon.de cannot be fetched (very easy to add those missing links though).</description>
<description lang="lt">Šis Scrapers renka informaciją iš šių palaikomų svetainių: MusicBrainz, last.fm, allmusic.com ir/arba amazon.de, o scrap meno kūrinius iš: fanart.tv, last.fm ir/arba allmusic.com. Jis gali būti nustatytas srities lauke, iš kurio puslapio norite, kad konkrečią informaciją paimtu. Pradinė paieška visada atliekama remiantis MusicBrainz. Jei Allmusic ir/ar amazon.de nuorodos nėra įdėtos MusicBrainz svetainėje, laukuose allmusic.com ir/arba amazon.de negali būti realūs (nors įtraukti tas trūkstamas grandis labai lengva).</description>
<description lang="mk">This scraper collects information from the following supported sites: MusicBrainz, last.fm, allmusic.com and amazon.de, while grabs artwork from: fanart.tv, last.fm and allmusic.com. It can be set field by field that from which site you want that specific information.⏎ ⏎ The initial search is always done on MusicBrainz. In case allmusic and/or amazon.de links are not added on the MusicBrainz site, fields from allmusic.com and/or amazon.de cannot be fetched (very easy to add those missing links though).</description>
<description lang="pl">Ten scraper zbiera informacje z następujących stron: MusicBrainz, last.fm, allmusic.com oraz amazon.de, a grafiki pobiera z: fanart.tv, last.fm i allmusic.com. Dla każdego pola z informacjami można wybrać osobną stronę. Początkowe wyszukiwanie jest zawsze wykonywane na MusicBrainz. Gdy linki do allmusic i/lub amazon.de nie są dodane na stronie MusicBrainz, nie można pobrać tych pól z allmusic.com i/lub amazon.de (pomimo to brakujące linki można bardzo łatwo dodać).</description>
<description lang="pt">Este scraper recolhe informações dos seguintes sítios: MusicBrainz, last.fm, allmusic.com e amazon.de, enquanto obtém artwork de: fanart.tv, last.fm e allmusic.com. Pode definir em cada campo o sítio do qual quer obter as informações. A pesquisa inicial é sempre feita no MusicBrainz. Caso os links allmusic e/ou amazon.de não tenham sido adicionados no sítio MusicBrainz, os campos do allmusic.com e/ou amazon.de não serão obtidos (no entanto, é muito fácil adicionar os links em falta). </description>
<description lang="pt_BR">Este scraper coleta informações nos seguintes sites: MusicBrainz, last.fm, allmusic.com e amazon.de, enquanto pega os artwork de: fanart.tv, last.fm and allmusic.com. O Mesmo pode ser configurara campo a campo em que site você quer obter informações específicas. A procura inicial é sempre feita no MusicBrainz. No caso allmusic e/ou amazon.de não terem sido adicionados no site MusicBrainz, campos de allmusic.com e/ou amazona.de não serão buscados (embora seja muito fácil adicionar os links faltantes). </description>
+ <description lang="ro">Acest scraper colectează informații de la următoarele site-uri suportate: MusicBrainz, last.fm, allmusic.com și amazon.de. Elementele de grafica sunt accesate din: fanart.tv, last.fm și allmusic.com. Acesta poate fi setat câmp cu câmp de pe care site doriți informațiile specifice sa fie colectate ⏎ ⏎ Căutarea inițială se face întotdeauna pe MusicBrainz. În cazul în care Allmusic și / sau link-ul amazon.de nu sunt adăugate pe MusicBrainz, câmpurile din allmusic.com și / sau amazon.de nu pot fi colectate (este foarte ușor însă de adăugat aceste legături lipsă).</description>
<description lang="ru">Данный скрапер собирает информацию со следующих сайтов: MusicBrainz, last.fm, allmusic.com и amazon.de, при этом получая обложки из: fanart.tv, last.fm и allmusic.com. Каждое поле можно настроить так, чтобы оно получало желаемую информацию с определенного вами сайта.⏎ ⏎ Начальный поиск всегда проводится на MusicBrainz. Для allmusic и/или amazon.de ссылки на сайт MusicBrainz отсутствуют, поля с allmusic.com и/или amazon.de не могут быть заполнены (добавить пропущенные ссылки очень просто).</description>
<description lang="sk">Tento zdroj získavania dát zbiera informácie z nasledujúcich podporovaných stránok: MusicBrainz, last.fm, allmusic.com a amazon.de, zatiaľ čo umeleckú grafiku získava zo stránok: fanart.tv, last.fm a allmusic.com. Je možné podrobne nastaviť, z ktorej stránky chcete získať konkrétne informácie. Počiatočné hľadanie je vždy na stránke MusicBrainz. V prípade, že odkaz pre allmusic a/alebo amazon.de nie je pridaný na stránku MusicBrainz, nie je možné získať údaje zo stránky allmusic.com a/alebo amazon.de (je ale veľmi jednoduché tieto chýbajúce odkazy pridať).</description>
<description lang="sl">Ta ponudnik združuje informacije s sledečih strani: MusicBrainz, last.fm, allmusic.com in amazon.de; grafike prenaša z: fanart.tv, last.fm in allmusic.com. Vsako informacijo lahko prenesete z različne strani. Začeno iskanje je vedn na MusicBrainz. V primeru, da na MusicBrainz ni povezave do allmusic.com in/ali amazon.de, informacij od tam ne morete prenesti (a to težavo hitro lahko odpravite).</description>
+[B]1.6.4[/B]
+Disable last.fm options because last.fm suspended XBMC's API account
+
+[B]1.6.3[/B]
+Updated language files from Transifex
+
[B]1.6.2[/B]
Updated language files from Transifex
--- /dev/null
+<?xml version="1.0" encoding="utf-8" standalone="yes"?>
+<!-- Translated using Transifex web application. For support, or if you would like to to help out, please visit your language team! -->
+<!-- Afrikaans language-Team URL: http://www.transifex.com/projects/p/XBMC-Main-Frodo/language/af/ -->
+<!-- Report language file syntax bugs at: http://trac.xbmc.org/ -->
+
+<strings>
+ <string id="30000">Gryp Album Duimnaelsketse vanaf fanart.tv</string>
+ <string id="30001">Gryp Album Duimnaelsketse vanaf Last.fm</string>
+ <string id="30002">Kry Album Resensie vanaf</string>
+ <string id="30003">Verkose Taal</string>
+ <string id="30004">Kry Album Gradering vanaf</string>
+ <string id="30005">Kry Album Style vanaf</string>
+ <string id="30006">Kry Album Stemmings vanaf</string>
+ <string id="30007">Kry Album Temas vanaf</string>
+ <string id="30008">Gryp Album Duimnaelsketse vanaf allmusic.com</string>
+ <string id="30009">Kunswerk</string>
+ <string id="30010">Gryp Album Duimnaelsketse vanaf theaudiodb.com</string>
+</strings>
<!-- Report language file syntax bugs at: http://trac.xbmc.org/ -->
<strings>
- <string id="30000">Hent album miniaturebilleder fra fanart.tv</string>
- <string id="30001">Hent album miniaturebilleder fra Last.fm</string>
- <string id="30002">Hent album anmeldelse fra</string>
- <string id="30003">Foretrukken sprog</string>
- <string id="30004">Hent album bedømmelse fra</string>
- <string id="30005">Hent album stilarter fra</string>
- <string id="30006">Hent album stemning fra</string>
- <string id="30007">Hent album temaer fra</string>
- <string id="30008">Hent album miniaturebilleder fra allmusic.com</string>
+ <string id="30000">Hent Miniaturebilleder til Albummer fra fanart.tv</string>
+ <string id="30001">Hent Miniaturebilleder til Albummer fra Last.fm</string>
+ <string id="30002">Hent Albumanmeldelse fra</string>
+ <string id="30003">Foretrukket Sprog</string>
+ <string id="30004">Hent Albumbedømmelse fra</string>
+ <string id="30005">Hent Albumstilarter fra</string>
+ <string id="30006">Hent Albumstemninger fra</string>
+ <string id="30007">Hent Albumtemaer fra</string>
+ <string id="30008">Hent Miniaturebilleder til Albummer fra allmusic.com</string>
<string id="30009">Illustrationer</string>
- <string id="30010">Hent album miniaturebilleder fra theaudiodb.com</string>
+ <string id="30010">Hent Miniaturebilleder til Albummer fra theaudiodb.com</string>
</strings>
--- /dev/null
+<?xml version="1.0" encoding="utf-8" standalone="yes"?>
+<!-- Translated using Transifex web application. For support, or if you would like to to help out, please visit your language team! -->
+<!-- Finnish language-Team URL: http://www.transifex.com/projects/p/XBMC-Main-Frodo/language/fi/ -->
+<!-- Report language file syntax bugs at: http://trac.xbmc.org/ -->
+
+<strings>
+ <string id="30000">Hae albumien pikkukuvat lähteestä fanart.tv</string>
+ <string id="30001">Hae albumien pikkukuvat lähteestä Last.fm</string>
+ <string id="30002">Hae albumien arvostelu lähteestä</string>
+ <string id="30003">Ensisijainen kieli</string>
+ <string id="30004">Hae albumien lähteestä</string>
+ <string id="30005">Hae albumien musiikkityylilajit lähteestä</string>
+ <string id="30006">Hae albumien mielentilat lähteestä</string>
+ <string id="30007">Hae albumien teemat lähteestä</string>
+ <string id="30008">Hae albumien pikkukuvat lähteestä allmusic.com</string>
+ <string id="30009">Kuvataide</string>
+ <string id="30010">Hae albumien pikkukuvat lähteestä theaudiodb.com</string>
+</strings>
<string id="30003">Idioma preferido</string>
<string id="30004">Obter a puntuación do álbum dende</string>
<string id="30005">Obter os estilos do álbum dende</string>
+ <string id="30006">Obter Sonoridades do Álbum de</string>
<string id="30007">Obter os temas do álbum dende</string>
<string id="30008">Obter as miniaturas do álbum dende allmusic.com</string>
<string id="30009">Ilustración</string>
--- /dev/null
+<?xml version="1.0" encoding="utf-8" standalone="yes"?>
+<!-- Translated using Transifex web application. For support, or if you would like to to help out, please visit your language team! -->
+<!-- Japanese language-Team URL: http://www.transifex.com/projects/p/XBMC-Main-Frodo/language/ja/ -->
+<!-- Report language file syntax bugs at: http://trac.xbmc.org/ -->
+
+<strings>
+ <string id="30000">fanart.tv からアルバムサムネールを取得</string>
+ <string id="30001">アルバムサムネールを Last.fm から取得</string>
+ <string id="30002">アルバムレビューの取得先</string>
+ <string id="30003">優先する言語</string>
+ <string id="30004">アルバム評価の取得先</string>
+ <string id="30005">アルバムスタイルの取得先</string>
+ <string id="30006">アルバムムードの取得先</string>
+ <string id="30007">アルバムテーマの取得先</string>
+ <string id="30008">アルバムサムネールを allmusic.com から取得</string>
+ <string id="30009">アートワーク</string>
+ <string id="30010">アルバムサムネールを theaudiodb.com から取得</string>
+</strings>
--- /dev/null
+<?xml version="1.0" encoding="utf-8" standalone="yes"?>
+<!-- Translated using Transifex web application. For support, or if you would like to to help out, please visit your language team! -->
+<!-- Romanian language-Team URL: http://www.transifex.com/projects/p/XBMC-Main-Frodo/language/ro/ -->
+<!-- Report language file syntax bugs at: http://trac.xbmc.org/ -->
+
+<strings>
+ <string id="30000">Colectează pictogramele albumelor din fanart.tv</string>
+ <string id="30001">Colectează pictogramele albumelor de la Last.fm</string>
+ <string id="30002">Descarcă review-urile albumelor de la </string>
+ <string id="30003">Limba preferată</string>
+ <string id="30004">Descarcă evaluarea albumelor de la </string>
+ <string id="30005">Descarcă stilul albumelor de la </string>
+ <string id="30006">Descarcă Album Moods de la </string>
+ <string id="30007">Descarcă tema albumelor de la </string>
+ <string id="30008">Colectează pictogramele albumelor de la allmusic.com</string>
+ <string id="30009">Pictograme</string>
+ <string id="30010">Colectează pictogramele albumelor de la theaudiodb.com</string>
+</strings>
<settings>
<category label="128">
<setting type="lsep" label="General Settings"/>
- <setting label="30002" type="labelenum" values="last.fm|TheAudioDb.com|amazon.de|None" id="albumreviewsource" default="last.fm"/>
- <setting label="30003" type="labelenum" values="en|de|es|fr|it|jp|pl|pt|ru|sv|tr|zh" id="lastfmlanguage" default="en" visible="eq(-1,0)"/>
+ <setting label="30002" type="labelenum" values="TheAudioDb.com|amazon.de|None" id="albumreviewsource" default="TheAudioDb.com"/>
<setting type="sep"/>
<setting label="30004" type="labelenum" values="MusicBrainz|allmusic.com|None" id="albumratingsource" default="MusicBrainz"/>
<setting label="30005" type="labelenum" values="allmusic.com|None" id="albumstylessource" default="allmusic.com"/>
<setting type="lsep" label="Artwork Settings"/>
<setting label="30000" type="bool" id="fanarttvalbumthumbs" default="true"/>
<setting label="30010" type="bool" id="tadbalbumthumbs" default="true"/>
- <setting label="30001" type="bool" id="lastfmalbumthumbs" default="true"/>
<setting label="30008" type="bool" id="allmusicalbumthumbs" default="true"/>
</category>
</settings>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="metadata.common.fanart.tv"
name="fanart.tv"
- version="2.1.0"
+ version="2.1.1"
provider-name="Team XBMC">
<requires>
<import addon="xbmc.metadata" version="1.0"/>
<expression />
</RegExp>
<RegExp input="$$1" output="\1" dest="16">
- <expression noclean="1">"artistthumb":\[(.*?)\],"</expression>
+ <expression noclean="1">"artistthumb":\[(.*?)\}\]</expression>
</RegExp>
<RegExp input="$$16" output="<thumb preview="\1/preview">\1</thumb>" dest="13">
<expression repeat="yes" noclean="1">"id":"\d*","url":"([^"]*)</expression>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="metadata.common.themoviedb.org"
name="The MovieDB common scraper functions"
- version="2.9.0"
+ version="2.9.1"
provider-name="Team XBMC">
<requires>
<import addon="xbmc.metadata" version="1.0"/>
<RegExp input="$$1" output="\1" dest="7">
<expression clear="yes" noclean="1">"cast":\[([^\]]*)</expression>
</RegExp>
- <RegExp input="$$7" output="<actor><name>\1</name><role>\2</role></actor>" dest="2">
- <expression repeat="yes" fixchars="1">"name":"([^"]*)","character":"([^"]*)</expression>
- </RegExp>
- <RegExp input="$$7" output="<actor><name>\1</name><role>\2</role><thumb>http://cf2.imgobject.com/t/p/original\3</thumb></actor>" dest="2+">
+ <RegExp input="$$7" output="<actor><name>\1</name><role>\2</role><thumb>http://cf2.imgobject.com/t/p/original\3</thumb></actor>" dest="2">
<expression repeat="yes" fixchars="1">"name":"([^"]*)","character":"([^"]*)","order":[0-9]*,"cast_id":[0-9]*,"profile_path":"([^"]*)"</expression>
</RegExp>
+ <RegExp input="$$7" output="<actor><name>\1</name><role>\2</role></actor>" dest="2+">
+ <expression repeat="yes" fixchars="1">"name":"([^"]*)","character":"([^"]*)","order":[0-9]*,"cast_id":[0-9]*,"profile_path":null</expression>
+ </RegExp>
<expression noclean="1" />
</RegExp>
</ParseTMDBCast>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="metadata.musicvideos.last.fm"
name="Last.fm for Music Videos"
- version="1.0.2"
+ version="1.0.3"
provider-name="Team XBMC">
<requires>
<import addon="xbmc.metadata" version="1.0"/>
<summary lang="bg">Сваля инф. за музикални клипове от Last.fm</summary>
<summary lang="ca">Arreplegador de videos musicals de Last.fm</summary>
<summary lang="cs">Zdroj dat hudebních videí Last.fm</summary>
- <summary lang="da">Last.fm musikvideo scraper</summary>
+ <summary lang="da">Last.fm Scraper til Musikvideor </summary>
<summary lang="de">Last.fm Musik Video Scraper</summary>
<summary lang="el">Scraper Μουσικών Βίντεο από Last.fm</summary>
<summary lang="en">Last.fm Music Video Scraper</summary>
<summary lang="es_MX">Scraper de Vídeos de Música de Last.fm</summary>
<summary lang="fi">Last.fm musiikkivideotietojen lataaja</summary>
<summary lang="fr">Collecter les clips vidéos Last.fm</summary>
+ <summary lang="gl">Scraper de música e vídeo de Last.fm</summary>
<summary lang="he">Last.fm וידאו קליפ מוזיקה</summary>
<summary lang="hu">Last.fm videóklip leolvasó</summary>
<summary lang="it">Scaper Video Musicale di Last.fm</summary>
+ <summary lang="ja">Last.fm 音楽ビデオスクレーパー</summary>
<summary lang="ko">Last.fm 뮤직 비디오 자료수집기</summary>
<summary lang="lt">Last.fm vaizdo klipų Scraper</summary>
<summary lang="mk">Last.fm Превземач на Музички видеа</summary>
<summary lang="nl">Last.fm Muziek Video Scraper</summary>
<summary lang="pl">Scraper teledysków Last.fm</summary>
<summary lang="pt">Scraper de música Last.fm</summary>
- <summary lang="pt_BR">Scraper de Concertos Last.FM</summary>
+ <summary lang="pt_BR">Scraper de Videoclipes do Last.fm</summary>
<summary lang="ro">Informaţii Videoclipuri de la Last.fm</summary>
<summary lang="ru">Инфоресурс видеоклипов Last.fm</summary>
<summary lang="se">Skrapa för Last.fm musik</summary>
<summary lang="th">ข้อมูลวิดีโอเพลงจาก Last.fm</summary>
<summary lang="zh">Last.fm音乐电视刮削器</summary>
<description lang="af">Laai Musiek Video inligting af</description>
+ <description lang="am">የሙዚቃ ቪዲዮ መረጃ ማውረጃ </description>
<description lang="be">Зладаваць зьвесткі пра музычнае відэа</description>
<description lang="bg">Сваля информация за музикални клипове</description>
<description lang="ca">Baixa informació de videos musicals</description>
<description lang="cs">Stáhnout informace o hudebním videu</description>
- <description lang="da">Hent musikvideo information</description>
+ <description lang="da">Hent information til Musikvideoer</description>
<description lang="de">Musik Video Information herunterladen</description>
<description lang="el">Λήψη πληροφοριών Μουσικών Βίντεο</description>
<description lang="en">Download Music Video information</description>
<description lang="es_MX">Descarga Información de Vídeos de Músca</description>
<description lang="fi">Lataa musiikkivideoiden tiedot</description>
<description lang="fr">Télécharger les informations des clips vidéos</description>
+ <description lang="gl">Descargar información da música e vídeo</description>
<description lang="he">הורד מידע וידאו קליפ</description>
<description lang="hu">Videóklip információk letöltése a Last.fm webhelyről</description>
<description lang="it">Scarica informazioni Video Musicale</description>
+ <description lang="ja">音楽ビデオ情報をダウンロード</description>
<description lang="ko">뮤직 비디오 정보 다운로드</description>
<description lang="lt">Atsisiusti vaizdo klipų informaciją</description>
<description lang="mk">Превземи информации за музичко видео</description>
<description lang="nl">Muziek Video informatie downloaden</description>
<description lang="pl">Pobieraj informacje o teledyskach z last.fm</description>
<description lang="pt">Descarregar informação de vídeoclipe</description>
- <description lang="pt_BR">Download de informação de Concertos</description>
+ <description lang="pt_BR">Baixar informações do videoclipe</description>
<description lang="ro">Descărcare informaţii videoclipuri</description>
<description lang="ru">Загружать сведения о видеоклипах</description>
<description lang="se">Ladda ner musikvideoinformation från last.fm</description>
<description lang="sv">Ladda ner musikvideoinformation</description>
<description lang="th">ดาวน์โหลดข้อมูลวิดีโอเพลง</description>
<description lang="zh">从Last.fm下载音乐电视信息</description>
+ <broken>broken due to last.fm suspended XBMC API Account</broken>
</extension>
</addon>
<!-- Report language file syntax bugs at: http://trac.xbmc.org/ -->
<strings>
- <string id="30000">Hent album miniaturebilleder fra fanart.tv</string>
- <string id="30001">Hent album miniaturebilleder fra Last.fm</string>
+ <string id="30000">Hent Miniaturebilleder af Albummer fra fanart.tv</string>
+ <string id="30001">Hent Miniaturebilleder af Albummer fra Last.fm</string>
</strings>
--- /dev/null
+<?xml version="1.0" encoding="utf-8" standalone="yes"?>
+<!-- Translated using Transifex web application. For support, or if you would like to to help out, please visit your language team! -->
+<!-- Galician language-Team URL: http://www.transifex.com/projects/p/XBMC-Main-Frodo/language/gl/ -->
+<!-- Report language file syntax bugs at: http://trac.xbmc.org/ -->
+
+<strings>
+ <string id="30000">Obter as miniaturas do álbum dende fanart.tv</string>
+ <string id="30001">Obter as miniaturas do álbum dende Last.fm</string>
+</strings>
--- /dev/null
+<?xml version="1.0" encoding="utf-8" standalone="yes"?>
+<!-- Translated using Transifex web application. For support, or if you would like to to help out, please visit your language team! -->
+<!-- Japanese language-Team URL: http://www.transifex.com/projects/p/XBMC-Main-Frodo/language/ja/ -->
+<!-- Report language file syntax bugs at: http://trac.xbmc.org/ -->
+
+<strings>
+ <string id="30000">fanart.tv からアルバムサムネールを取得</string>
+ <string id="30001">Last.fm からアルバムサムネールを取得</string>
+</strings>
<strings>
<string id="30000">Obter miniaturas de álbuns de fanart.tv</string>
- <string id="30001">Obter miniaturas de álbuns de Last.fm</string>
+ <string id="30001">Obter miniaturas de álbuns do Last.fm</string>
</strings>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="metadata.themoviedb.org"
name="The Movie Database"
- version="3.7.1"
+ version="3.7.2"
provider-name="Team XBMC">
<requires>
<import addon="xbmc.metadata" version="1.0"/>
<summary lang="bg">Сваля инф. за филми от TMDB</summary>
<summary lang="ca">Arreplegador de pel·lícules TMDB</summary>
<summary lang="cs">Zdroj zisku dat k filmům TMDB</summary>
- <summary lang="da">TMDB film-scraper</summary>
+ <summary lang="da">TMDB Scraper til Film</summary>
<summary lang="de">Film Scraper für TheMovieDB</summary>
<summary lang="el">Scraper Ταινιών του TMDb</summary>
<summary lang="en">TMDB Movie Scraper</summary>
<summary lang="et">TMDB Filmi kaabits</summary>
<summary lang="fi">TMDB elokuvatietojen lataaja</summary>
<summary lang="fr">Collecteur de Films TMDB</summary>
+ <summary lang="gl">Scraper de Filme de TMDB</summary>
<summary lang="he">TMDB סקרייפר סרטים</summary>
<summary lang="hu">TMDB filmadat leolvasó</summary>
<summary lang="it">Ricercatore per TMDB Movie</summary>
<summary lang="nl">TMDb-filmscraper</summary>
<summary lang="pl">Scraper filmowy TMDB</summary>
<summary lang="pt">Scraper de filmes TMDb</summary>
- <summary lang="pt_BR">Scraper Filmes TMDB</summary>
+ <summary lang="pt_BR">Scraper de Filmes TMDb</summary>
<summary lang="ro">Scraper de filme TMDb</summary>
<summary lang="ru">Инфоресурс на базе TMDB</summary>
<summary lang="se">Skrapa för TMDb</summary>
<description lang="bg">themoviedb.org е безплатна и отворена база от данни за филми. Задвижва се изцяло от потребителите си - хора като вас. Милиони хора по света всеки месец ползват TMDb, благодарение на многофункционалното API. Едновременно с това е и източник на информация за медийни центрове, като XBMC, които извличат метаинформация за филми, постери и Fanart.</description>
<description lang="ca">themoviedb.org és una base de dades oberta i lliure. Es manté completament per usuaris, per gent com tu. TMDb s'utilitza actualment per milions de persones cada mes i amb la seva potent API també és utilitzada per molts centre media populas com l'XBMC per obtenir les meta-dades de pel·lícules, pósters i fanart per enriquir l'experiència d'usuari.</description>
<description lang="cs">themoviedb.org je otevřená a svobodná databáze filmů. Je řízena uživateli, tedy lidmi jako vy. TMDB je každý měsíc používána milióny lidí a se svým výborným API je také používano mnoha populárními mediálními centry jako je XBMC, k získávání metadat, plagátů a fanartu filmů pro obohacení uživatelského zážitku.</description>
- <description lang="da">themoviedb.org er en fri og åben filmdatabase. Den er helt og aldeles brugerdrevet af folk som dig. TMDb bruges i øjeblikket af millioner af mennesker hver måned, og med deres kraftfulde API er den også brugt af mange populære mediecentre som XBMC til at hente metadata, plakater og fankunst til film, for at berige brugerens oplevelse.</description>
+ <description lang="da">themoviedb.org er en fri og åben filmdatabase. Den er helt og aldeles brugerdrevet af folk som dig. TMDb bruges i dag af millioner af mennesker hver måned, og med deres kraftfulde API er den også brugt af mange populære mediecentre såsom XBMC til at hente metadata, plakater og fankunst til film, for at berige brugerens oplevelse.</description>
<description lang="de">TheMovieDB.org ist eine freie und offene Filmdatenbank. Sie wird von Benutzern wie Dir betrieben. TheMovieDB wird zur Zeit monatlich von Millionen genutzt. Mit der mächtigen API wird sie auch von vielen Media-Center, wie z.B. XBMC, genutzt um Film Metadaten, Poster und Fanarts zu beziehen, damit der Benutzer sein Media-Center verschönern kann.</description>
<description lang="el">Το themoviedb.org είναι μία δωρεάν και ανοικτή βάση δεδομένων ταινιών. Διαχειρίζεται πλήρως από ανθρώπους σαν και εσάς. Το TMDb χρησιμοποιείται από εκατομμύρια ανθρώπους κάθε μήνα, και με το πανίσχυρο API τους, χρησιμοποιείται και από πολλά δημοφιλή κέντρα πολυμέσων όπως το XBMC για να λαμβάνουν μετα-δεδομένα Ταινιών, Αφίσες και Fanart για εμπλουτισμό της εμπειρίας του χρήστη.</description>
<description lang="en">themoviedb.org is a free and open movie database. It's completely user driven by people like you. TMDb is currently used by millions of people every month and with their powerful API, it is also used by many popular media centers like XBMC to retrieve Movie Metadata, Posters and Fanart to enrich the user's experience.</description>
<description lang="et">themoviedb.org on tasuta ja vaba juurdepääsuga filmide andmebaas.See on täielikult koostatud sinu sarnaste inimeste poolt. TMDb on hetkel igapäevaselt kasutuses miljonite inimeste poolt ja seda kasutavad paljud populaarsed meediakeskused, nagu ka XBMC, filmiandmete, fännikunsti ja posterite leidmiseks.</description>
<description lang="fi">themoviedb.org on vapaa ja avoin elokuvatietokanta. Se on täysin käyttäjien ylläpitämä. TMDb:a käyttää miljoonat ihmiset joka kuukausi, sitä käytetään käyttäjäkokemuksen rikastuttamiseen monissa muissakin suosituissa XBMC:n kaltaisissa ohjelmissa lataamalla elokuvien tietoja, julisteita ja fanitaidetta.</description>
<description lang="fr">themoviedb.org est une base de données gratuite et libre d'accès de films. Elle est totalement gérée par les utilisateurs. TMDb est actuellement utilisée par des millions de personnes chaque mois et, grâce à ses puissantes fonctions et routines, elle est également utilisée par de nombreux Media Centers populaires comme XBMC aux fins de récupération des Métadonnées, Posters et Fanarts des films en vue d'améliorer l'expérience de l'utilisateur.</description>
+ <description lang="gl">themoviedb.org é una base de datos sobre filmes libre e aberta, impulsada por xente coma ti. Na actualidade TMDb é usada por millóns de persoas todos os meses, e grazas ó seu potente API, usado por algúns dos centros multimedia máis populares como XBMC para obter a información dos filmes, Posters e Fanart e así mellorar a experiencia do usuario.</description>
<description lang="he">themoviedb.org is a free and open movie database. It's completely user driven by people like you. TMDb is currently used by millions of people every month and with their powerful API, it is also used by many popular media centers like XBMC to retrieve Movie Metadata, Posters and Fanart to enrich the user's experience.</description>
<description lang="hu">A themoviedb.org egy ingyenes és nyílt filmadatbázis. Teljes egészében olyan felhasználók töltik fel, mint például Te. A TMDb-t havonta emberek milliói használják és a hatékony API-n keresztül számos népszerű média center is, mint például az XBMC a filmadatok, poszterek, fanartképek letöltésére.</description>
<description lang="it">themoviedb.org è un database libero e aperto. E' gestito da utenti e persone come te. Viene usato correntemente da millioni di persone ogni mese e con le sue potenti API, è anche utilizzato da molti popolari media centers come XBMC per ottenere Metadati, Posters e Fanart di film per arricchire la loro esperienza utente.</description>
+ <description lang="ja">themoviedb.org は自由でオープンな映画データベースです。その運営は、完全にユーザ主導で行われています。TMDb は現在毎月何百万人もの人に利用されているほか、XBMC のようなメディアセンターも、TMDb のパワフルな API を介して映画メタデータ、ポスター、ファンアートを取得し、使い勝手を向上させるなどして使っています。</description>
<description lang="ko">themoviedb.org 는 전적으로 여러분 같은 이용자가 주도하는 무료 공개 영화 데이터베이스입니다. TMDb 는 현재 매월 수백만의 사람들이 강력한 API와 함께 사용하고 있을 뿐만 아니라 XBMC 같은 많은 미디어 센터가 영화 정보, 포스터, 팬아트를 가져와 풍성한 사용자 경험을 제공하는데 이용되고 있습니다.</description>
<description lang="lt">themoviedb.org yra nemokama ir atvira Filmų duomenų bazė. Jis remiamas ir skaitinamas tokių pat vartotojų kaip ir jūs. TMDb šiuo metu naudoja milijonai žmonių kiekvieną mėnesį, ir turi galingą API (informacinį centrą), taip pat naudojamas daug populiarių žiniasklaidos centrų. Pavyzdžiui XBMC gauti filmo(-ų) metaduomenis, plakatus ir FanArt siekiant praturtinti vartotojo patirtį.</description>
<description lang="mk">themoviedb.org is a free and open movie database. It's completely user driven by people like you. TMDb is currently used by millions of people every month and with their powerful API, it is also used by many popular media centers like XBMC to retrieve Movie Metadata, Posters and Fanart to enrich the user's experience.</description>
<description lang="nl">Themoviedb.org is een vrije en open filmdatabank. Gebruikers zoals jij vormen de motor van deze site. Op dit moment gebruiken elke maand miljoenen mensen TMDb. De krachtige API van de site laat mediacenters zoals XBMC toe om metadata, posters en fanart op te halen en zo de gebruikerservaring te verrijken.</description>
<description lang="pl">themoviedb.org jest wolną i otwartą filmową bazą danych. Jest zarządzana całkowicie przez osoby takie jak Ty. Obecnie odwiedzają ją miliony osób miesięcznie, a dzięki dostępności API jest używana w wielu media center, takich jak XBMC, do pobierania informacji o filmach, plakatów i fanartów.</description>
<description lang="pt">O themoviedb.org é uma base de dados de filmes livre e aberta. É actualizado inteiramente por pessoas como você e usado por milhões todos os meses. Com o poderoso motor de busca disponível, também se tornou no favorito para muitos programas de centro de média, como o popular XBMC, para obter informação, posters e fanart que enriquecem a experiência do utilizador.</description>
- <description lang="pt_BR">themoviedb.org é um banco de dados aberto e gratuito. É completamente operacionalizado por pessoas como você. TMDb é usado atualmente por milhares de pessoas a cada mês e com sua poderosa API, é usado por muitos media centers como XBMC para buscar Metadados de Filtes, Posters e Fanart para enriquecer a experiência de uso.</description>
+ <description lang="pt_BR">O themoviedb.org é um banco de dados aberto e gratuito. É completamente operado por pessoas como você. O TMDb é atualmente usado por milhares de pessoas a cada mês e com sua poderosa API, é usado por muitas centrais de mídia populares como o XBMC para buscar metadados, cartazes e fanart de filmes para enriquecer a experiência do usuário.</description>
<description lang="ro">themoviedb.org este o bază de date de filme gratuită și deschisă. Este actualizată de oameni ca și tine. TMDb este curent folosită de milioane de oameni în fiecare lună și datorită API-ului puternic, este de asemena folosită de multe centre media populare ca XBMC pentru a obține informații despre filme, afișe și imagini produse de fani (Fanart) care îmbogățesc experiența utilizatorului.</description>
<description lang="ru">themoviedb.org — это бесплатная и открытая база данных фильмов. Она полностью поддерживается обычными людьми. В данный момент сайт TMDb используют миллионы людей каждый месяц, и благодаря мощному API его могут использовать различные популярные медиацентры, такие как XBMC, чтобы получать метаданные, постеры и фанарт для фильмов с целью красивого оформления интерфейса.</description>
<description lang="se">themoviedb.org är en fri och öppen filmdatabas. Det drivs helt av människor som dig. TMDb används av miljontals människor varje månad och med deras kraftfulla API, är det också använt av många populära mediacenter som XBMC för att hämta filmmetadata, omslag och fanart för att förgylla användarens upplevelse.</description>
<description lang="sk">themoviedb.org je voľná a otvorená databáza filmov. Je výlučne vedená užívateľmi ako si ty. TMDb je každý mesiac používaná miliónmi ľudí, a vďaka svojmu výkonnému API je veľmi populárna medzi 'media centrami' ako XBMC pre sťahovanie filmových metadát, plagátov a fanartov pre obohatenie zážitkov.</description>
<description lang="sl">themoviedb.org je brezplačna in prosta baza filmov. Je v celoti ustvarjena s strani uporabnikov kot ste vi. TMDB uporablja miljone ljudi in z njihovo močno API, jo lahko uporabljate tudi v multimedijskih centrih kot XBMC, s tem prenesete informacije o filmih, plakate in ozadja ter s tem popestrite uporabniško izkušnjo.</description>
- <description lang="sv">themoviedb.org är en gratis och öppen filmdatabas. Den drivs helt av människor som du. TMDb används för närvarande av miljoner människor varje månad och med deras kraftfulla API används den även av många populära mediacenter som t.ex. XBMC för att hämta metadata om filmen, affischer och fanart för att berika användarens upplevelse.</description>
+ <description lang="sv">themoviedb.org är en gratis och öppen filmdatabas. Den drivs helt av människor som du. TMDb används för närvarande av miljoner människor varje månad och med deras kraftfulla API används den även av många populära mediacenter som t.ex. XBMC för att hämta metadata om filmer, affischer och fanart för att berika användarens upplevelse.</description>
<description lang="th">themoviedb.org เป็นฐานข้อมูลภาพยนตร์ที่ฟรีและเปิดกว้าง. มันถูกขับเคลื่อนจากผู้ใช้ โดยคนเช่นคุณ. TMDb ปัจจุบันมีการใช้โดยคนนับล้านในแต่ละเดือน และมีประสิทธิภาพด้วย API ของพวกเขา , มันยังถูกใช้โดยหลายศูนย์สื่อที่นิยมเช่น XBMC เพื่อดึง อธิบายข้อมูลภาพยนตร์, โปสเตอร์และ แฟนอาร์ต เพื่อเพิ่มประสบการณ์ของผู้ใช้</description>
<description lang="zh">themoviedb.org是一个开放和自由的电影数据库。它完全由象你一样的用户来掌控。TMDb每月为上百万用户提供服务,并通过强大的API界面为许多流行的媒体中心系统如XBMC提供电影资料、封面海报和同人画以丰富用户的体验。</description>
<platform>all</platform>
+[B]3.7.2[/B]
+- updated language files from Transifex
+
[B]3.7.1[/B]
- updated language files from Transifex
--- /dev/null
+<?xml version="1.0" encoding="utf-8" standalone="yes"?>
+<!-- Translated using Transifex web application. For support, or if you would like to to help out, please visit your language team! -->
+<!-- Chinese (Traditional) language-Team URL: http://www.transifex.com/projects/p/XBMC-Main-Frodo/language/zh_TW/ -->
+<!-- Report language file syntax bugs at: http://trac.xbmc.org/ -->
+
+<strings>
+ <string id="30000">啟用 Fanart</string>
+</strings>
<!-- Report language file syntax bugs at: http://trac.xbmc.org/ -->
<strings>
- <string id="30000">Aktiver fankunst</string>
- <string id="30001">Foretræk trailere fra HD-trailers.net</string>
- <string id="30002">Foretrukket sprog</string>
+ <string id="30000">Aktivér Fankunst</string>
+ <string id="30001">Foretræk Trailer fra HD-trailers.net</string>
+ <string id="30002">Foretrukket Sprog</string>
<string id="30003">Hent bedømmelse fra</string>
- <string id="30004">Aktiver trailer (YouTube)</string>
+ <string id="30004">Aktivér Trailer (YouTube)</string>
<string id="30005">Behold original titel</string>
- <string id="30006">Foretrukket certificeringsland</string>
+ <string id="30006">Foretrukket land til aldersvejledninger</string>
</strings>
--- /dev/null
+<?xml version="1.0" encoding="utf-8" standalone="yes"?>
+<!-- Translated using Transifex web application. For support, or if you would like to to help out, please visit your language team! -->
+<!-- Galician language-Team URL: http://www.transifex.com/projects/p/XBMC-Main-Frodo/language/gl/ -->
+<!-- Report language file syntax bugs at: http://trac.xbmc.org/ -->
+
+<strings>
+ <string id="30000">Habilitar Fanart</string>
+ <string id="30001">Preferir os Avances de HD-Trailers.net</string>
+ <string id="30002">Idioma Preferido</string>
+ <string id="30003">Obter a puntuación dende</string>
+ <string id="30004">Habilitar Avance (YouTube)</string>
+ <string id="30005">Manter o Título Orixinal</string>
+ <string id="30006">País de Certificación Preferido</string>
+</strings>
<strings>
<string id="30000">ファンアートを有効にする</string>
<string id="30001">HD-Trailers.net の予告編を優先</string>
- <string id="30002">優先言語</string>
+ <string id="30002">優先する言語</string>
<string id="30003">評価の取得先</string>
<string id="30004">予告編を有効にする (YouTube)</string>
<string id="30005">オリジナルタイトルを保持</string>
+ <string id="30006">優先する証明書の国</string>
</strings>
<strings>
<string id="30000">Ativar Fanart</string>
- <string id="30001">Preferir Trailer de HD-Trailers.net</string>
- <string id="30002">Linguagem Preferida</string>
- <string id="30003">Pegar classificação de</string>
- <string id="30004">Ativar Trailer (YouTube)</string>
- <string id="30005">Manter o Título Original</string>
- <string id="30006">Preferir Certificação do País</string>
+ <string id="30001">Preferir trailer de HD-Trailers.net</string>
+ <string id="30002">Idioma preferido</string>
+ <string id="30003">Obter classificação de</string>
+ <string id="30004">Ativar trailer (YouTube)</string>
+ <string id="30005">Manter o título original</string>
+ <string id="30006">Preferir país de certificação</string>
</strings>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="metadata.tvdb.com"
name="The TVDB"
- version="1.4.3"
+ version="1.4.5"
provider-name="Team XBMC">
<requires>
<import addon="xbmc.metadata" version="1.0"/>
<summary lang="be">Fetch TV show metadata from TheTVDB.com</summary>
<summary lang="bg">Изтегля информация за ТВ Сериали от TheTVDB.com</summary>
<summary lang="cs">Získat metadata televizního pořadu z TheTVDB.com</summary>
- <summary lang="da">Hent TV serie metadata fra TheTVDB.com</summary>
+ <summary lang="da">Hent metadata til TV-serier fra TheTVDB.com</summary>
<summary lang="de">Scraper für Fernsehserien von TheTVDB.com</summary>
<summary lang="el">Λήψη μετα-δεδομένων σειρών από το TVDB.com</summary>
<summary lang="en">Fetch TV show metadata from TheTVDB.com</summary>
<summary lang="es_MX">Obtener metadata de Series desde TheTVDB.com</summary>
<summary lang="fi">Nouda TV-ohjelmien tietoja TheTVDB.com -sivustolta</summary>
<summary lang="fr">Collecter les métadonnées des séries TV depuis TheTVDB.com</summary>
+ <summary lang="gl">Obter os metadatos das series de TV dende TheTVDB.com</summary>
<summary lang="he">הבא מידע </summary>
<summary lang="hu">TV műsor információk a TheTVDB.com-ról</summary>
<summary lang="is">Sækja Sjónvarpsþátta lýsigögn frá TheTVDB.com</summary>
<summary lang="it">Scarica informazioni programma TV da TheTVDB.com</summary>
+ <summary lang="ja">TheTVDB.com からTV番組メタデータを取得</summary>
<summary lang="ko">TheTVDB.com 에서 TV 쇼 정보 가져오기</summary>
<summary lang="lt">Parsiųsti TV šou (serialo) metaduomenis iš TheTVDB.com</summary>
<summary lang="mk">Превземи ТВ шоу метаподаток од TVDB.com</summary>
<summary lang="nl">Seriemetadata ophalen van TheTVDB.com</summary>
<summary lang="pl">Pobieraj dane o serialach TV z TheTVDB.com</summary>
<summary lang="pt">Obtenha dados para séries TV de TVDB.com</summary>
- <summary lang="pt_BR">Obtenha dados dos Seriados de TheTVDB.com</summary>
+ <summary lang="pt_BR">Obtenha dados dos seriados do TheTVDB.com</summary>
<summary lang="ro">Obţineţi datele despre seriale de la TheTVDB.com</summary>
<summary lang="ru">Загрузка информации о ТВ-шоу с TheTVDB.com</summary>
<summary lang="se">Skrapa för TV-seriemetadata från TVDB.com</summary>
<summary lang="th">เรียกข้อมูลหลักของรายการทีวี จาก TheTVDB.com</summary>
<summary lang="uk">Отримання відомостей про серіали із TheTVDB.com</summary>
<summary lang="zh">从TheTVDB.com获取电视剧集信息</summary>
+ <summary lang="zh_TW">從 TheTVDB.com 取得電視節目資訊</summary>
<description lang="af">TheTVDB.com is 'n TV Skraper. Die werf is 'n massiewe oop databasis wat deur enige iemand verander kan word en bevat vol meta data vir baie vertonings in verskillende tale. Alle inhoud en prente op die werf is bygedra deur sy gebruikers vir sy gebruikers en het 'n hoë standaard van kwaliteit. Die databasis skema en webruimte is oopbron onder die GPL.</description>
<description lang="be">TheTVDB.com is a TV Scraper. The site is a massive open database that can be modified by anybody and contains full meta data for many shows in different languages. All content and images on the site have been contributed by their users for users and have a high standard or quality. The database schema and website are open source under the GPL.</description>
<description lang="bg">TheTVDB.com предоставя информация за ТВ Сериали. Сайтът представлява една огромна, безплатна и отворена база от данни. Всеки може да редактира (наличната информация) или да добавя нова. Съдържа подробни данни за много сериали и на различни езици. Цялото съдържание на сайта и публикуваните изображения са предоставени от потребители за потребителите. Съдържанието на сайтът и базата от данни е с отворен код под лиценза GPL.</description>
<description lang="cs">TheTVDB.com je zdroj metadat pro televizní pořady. Stránka je masivní otevřená databáze, kterou může kdokoliv upravovat a obsahuje veškerá metadata v různých jazycích k mnoha pořadům. Všechen obsah a obrázky jsou poskytnuty uživateli pro uživatele a mají vysokou kvalitu. Návrhový vzor databáze a webová stránka jsou svobodný software licencovaný pod GPL.</description>
- <description lang="da">TheTVDB.com er en TV-scraper. Siden er en massiv åben database, som alle kan modificere, og den indeholder fyldestgørende metadata for mange serier på forskellige sprog. Alt indhold og billeder på siden er tilføjet af brugere og har en høj standard eller kvalitet. Databasens model og hjemmeside er open source under GPL.</description>
+ <description lang="da">TheTVDB.com er en TV-scraper. Siden er en massiv åben database, som alle kan modificere, og den indeholder fyldestgørende metadata for mange serier på forskellige sprog. Alt indhold og alle billeder på siden er tilføjet af brugere og har en høj standard eller kvalitet. Databasens model og webside er open source under GPL.</description>
<description lang="de">TheTVDB.com ist ein Scraper für TV-Serien. Die Seite hat eine riesige offene Datenbank, welche von jedem geändert werden kann. Sie enthält weitreichende Meta-Daten für viele TV-Serien in verschiedenen Sprachen. Alle Inhalte und Bilder dieser Seite stammen von User für User und haben eine hohe Qualität. Das Datenbankschema und die Webseite sind OpenSource unter dem GPL-Recht</description>
<description lang="el">Το TVDB.com είναι ένα Scraper Τηλεόρασης. Η ιστοσελίδα είναι μία τεράστια ανοικτή βάση δεδομένων η οποία μπορεί να τροποποιηθεί από τον καθένα και περιέχει πλήρη μετα-δεδομένα για πολλές σειρές σε διάφορες γλώσσες. Όλο το περιεχόμενο και οι εικόνες στην ιστοσελίδα προέρχονται από τους χρήστες του, και έχουν υψηλά πρότυπα ποιότητας. Το σχήμα της βάσης δεδομένων και της ιστοσελίδας είναι ανοικτού κώδικα υπό το GPL.</description>
<description lang="en">TheTVDB.com is a TV Scraper. The site is a massive open database that can be modified by anybody and contains full meta data for many shows in different languages. All content and images on the site have been contributed by their users for users and have a high standard or quality. The database schema and website are open source under the GPL.</description>
<description lang="es_MX">TheTVDB.com es un scraper de series de TV. Este sitio es una base de datos masiva y abierta que puede ser modicada por cualquiera y contiene metadata completa para muchas series en diferentes lenguajes. Todo el contenido e imágenes del sitio han sido contribuidos por sus usuarios para usuarios, los cuales tienen un alto estándar de calidad. El esquema de la base de datos y el sitio web son de código abierto bajo GPL.</description>
<description lang="fi">TheTVDB.com on TV-ohjelmatietojen lataaja. Sivusto on massiivinen avoin tietokanta, jonka tietoja kaikki pystyy muokkaamaan ja se sisältää täydet tiedot monista TV-ohjelmista usealla eri kielellä. Kaikki sivuston sisältö ja kuvat on korkealaatuisia ja käyttäjien lahjoittamia. Tietokanta ja sivusto ovat GPL-lisenssin alaisia.</description>
<description lang="fr">TheTVDB.com est un collecteur TV. Le site est une immense base de données libre pouvant être modifiée par tout le monde et contenant toutes les informations de nombreuses séries en différentes langues. Les informations et images du site proviennent de contributions d'utilisateurs pour les usagers et sont d'excellentes qualités. Le schéma de la base de données et le site Web sont en open source sous licence GPL.</description>
+ <description lang="gl">TheTVDB.com é un Scraper de TV. O sitio é unha enorme base de datos aberta que calquera pode modificar e contén todos os metadatos de centos de series en diferentes idiomas. Todo o contido e imaxes do sitio foi proporcionado polos seus usuarios e amosa un grande estándar de calidade. O esquema da base de datos e o sitio web son de código aberto baixo licenza GPL.</description>
<description lang="he">TheTVDB.com הוא סקרייפר עבור תוכניות טלוויזיה. אתר זה הוא מסד נתונים פתוח עצום אשר כל אחד יכול לערוך ומכיל פרטים מלאים להרבה תוכניות טלוויזיה בשפות שונות. כל התכנים והתמונות באתר נתרמו ע"י המשתמשים שלה עבור משתמשים וישנו סטנדרט גבוה או איכות גבוהה. תכנית המסד נתונית והאתר הם בקוד פתוח תחת ה-GPL.</description>
<description lang="hu">A TheTVDB.com egy TV műsor leolvasó. Az oldal egy hatalmas adatbázis amit bárki szabadon módosíthat és rengeteg TV műsor adatait tartalmazza különböző nyelveken. A minőségi tartalom és képanyag ezen az oldalon felhasználóktól származik más felhasználók számára. Az adatbázisstruktúra és a weblap nyílt forráskódú a GPL licenc alatt.</description>
<description lang="it">TheTVDB.com è uno scraper di programmi TV. Questo sito è un enorme database che può essere modificato da chiunque, e contiene tutte le informazioni di molti show in varie lingue. Tutti i contenuti e le immagini sul sito sono stati forniti dagli utenti, ed hanno un alto standard di qualità. Lo schema del database e il sito internet sono open source sotto licenza GPL.</description>
+ <description lang="ja">TheTVDB.com は TV スクレーパーです。本サイトは強力でオープンなデータベースで、誰もが追加修正可能です。テレビ番組のメタデータは多くの言語で提供されています。番組情報や画像データはユーザにより提供され、そのクオリティは非常に高いものです。データベーススキーマと web サイトは GPL ライセンスのオープンソースです。</description>
<description lang="ko">TheTVDB.com 은 TV 자료수집기입니다. 위 사이트는 이용자가 직접 수정할 수 있으며 다양한 언어의 완전한 메타 데이터를 포함하는 방대한 오픈 데이터베이스입니다. 사이트의 모든 콘텐츠와 이미지들은 사이트 이용자들이 다른 이용자들을 위해 제공한 것이며 높은 품질을 가지고 있습니다. 웹사이트와 데이터베이스 스키마는 GLP 오픈소스입니다.</description>
<description lang="lt">TheTVDB.com yra TV Scraper. Ši svetainė yra didžiulė atvira duomenų bazė, kuria gali pakeisti bet kas ir kurioje visa meta duomenis bazė daugelo šou įvairiomis kalbomis. Visas svetainės turinys atnaujinamas/papyldomasvartotojų ir turi aukštą kokybės standartą. Duomenų bazės ir svetainės yra atviro kodo pagal GPL standartą.</description>
<description lang="mk">TheTVDB.com is a TV Scraper. The site is a massive open database that can be modified by anybody and contains full meta data for many shows in different languages. All content and images on the site have been contributed by their users for users and have a high standard or quality. The database schema and website are open source under the GPL.</description>
<description lang="nl">TheTVDB.com is een seriescraper. De site is een gigantische open databank die iedereen kan aanpassen en uitbreiden, en biedt metadata aan voor TV-series in verschillende talen. Alle inhoud en afbeeldingen zijn afkomstig van gebruikers en moeten een grondige kwaliteitscontrole doorstaan. Het databankschema en de websitecode zijn vrijgegeven onder de open source GPL-licentie.</description>
- <description lang="pl">TheTVDB.com jest scraperem TV. Ten serwis jest ogromną, wielojęzykową i otwartą bazą danych o serialach, którą może edytować każdy. Zawartość tego serwisu została dostarczona przez użytkowników, dla użytkowników. Dostępne materiały mają wysoki standard lub jakość.</description>
+ <description lang="pl">TheTVDB.com jest scraperem TV. Ten serwis jest ogromną, wielojęzykową i otwartą bazą danych o serialach, którą może edytować każdy. Zawartość tego serwisu została dostarczona przez użytkowników, dla użytkowników. Dostępne materiały są najwyższej jakości.</description>
<description lang="pt">O TheTVDB.com é um scraper para séries TV. O site é uma gigantesca base de dados livre que pode ser alterada por qualquer pessoa e contém informação em várias línguas. Todos os conteúdos do site foram enviados por utilizadores e têm um elevado padrão de qualidade. A base de dados e o site funcionam sob uma licença GPL de software livre.</description>
- <description lang="pt_BR">TheTVDB.com é um scraper de Seriados. O site é um enorme banco de dados aberto que pode ser modificado por qualquer pessoa e contém dados completos de muitos seriados em diferentes idiomas. Todos os conteúdos e imagens no site foram contribuições de usuários para usuários e têm um alto padrão de qualidade. O esquema do banco de dados e do site são de código aberto sob a licença GPL.</description>
+ <description lang="pt_BR">TheTVDB.com é um scraper de seriados. O site é um enorme banco de dados aberto que pode ser modificado por qualquer pessoa e contém metadados completos de muitos seriados em diferentes idiomas. Todos os conteúdos e imagens no site foram contribuições de usuários para usuários e têm um alto padrão de qualidade. O esquema do banco de dados e do site são de código aberto sob a licença GPL.</description>
<description lang="ro">TheTVDB.com este un colectore TV. Site-ul este o bază de date masivă, deschisă, ce poate fi modificată de oricine şi conţine date complete despre multe seriale în diferite limbi. Tot conţinutul site-ului a fost contribuit de către utilizatori pentru utilizatori şi are un standard de caliate înalt. Schema bazei de date şi site-ul au sursele libere sub licenţă GPL.</description>
<description lang="ru">TheTVDB.com — это инфоресурс для сериалов. Сайт представляет собой огромную открытую базу данных, в которую может вносить данные любой пользователь и которая содержит полные мета-данные для многих сериалов на различных языках. Всё содержимое и изображения на сайте были выложены его пользователями для других пользователей и имеют высокий уровень качества. Структура базы данных и веб-сайт созданы с открытым исходным кодом по лицензии GPL.</description>
<description lang="se">TVDB.com är en TV-skrapa. Sajten är en massiv öppen databas som kan ändras av vem som helst och innehåller full metadata för mängder av serier på olika språk. Allt innehåll och bilder på sajten har bidragits av användare för användare och har en hög standard eller kvalitet. Databasschemat och webbsidan är licensierat som GPL.</description>
+[B]1.4.5[/B]
+- Fixed: dvdorder setting won't apply in GetEpisodeDetails (thx and credits to scudlee)
+
+[B]1.4.4[/B]
+- Updated: language files from Transifex
+
[B]1.4.3[/B]
- Updated: language files from Transifex
--- /dev/null
+<?xml version="1.0" encoding="utf-8" standalone="yes"?>
+<!-- Translated using Transifex web application. For support, or if you would like to to help out, please visit your language team! -->
+<!-- Amharic language-Team URL: http://www.transifex.com/projects/p/XBMC-Main-Frodo/language/am/ -->
+<!-- Report language file syntax bugs at: http://trac.xbmc.org/ -->
+
+<strings>
+ <string id="30004">ቋንቋ </string>
+</strings>
<!-- Report language file syntax bugs at: http://trac.xbmc.org/ -->
<strings>
+ <string id="30000">使用 DVD 順序</string>
+ <string id="30001">使用原本的順序 (單季)</string>
+ <string id="30002">啟用 Fanart</string>
+ <string id="30003">下載海報</string>
<string id="30004">語言</string>
</strings>
<strings>
<string id="30000">Brug DVD rækkefølge</string>
<string id="30001">Brug absolut rækkefølge (enkelt sæson)</string>
- <string id="30002">Aktivér fankunst</string>
- <string id="30003">Foretræk plakater</string>
+ <string id="30002">Aktiver Fankunst</string>
+ <string id="30003">Foretræk Plakater</string>
<string id="30004">Sprog</string>
</strings>
<!-- Report language file syntax bugs at: http://trac.xbmc.org/ -->
<strings>
+ <string id="30000">Use DVD Order</string>
+ <string id="30001">Usar Oden Absoluto (Só unha tempada)</string>
+ <string id="30002">Habilitar Fanart</string>
+ <string id="30003">Preferir Carteis</string>
<string id="30004">Idioma</string>
</strings>
<!-- Report language file syntax bugs at: http://trac.xbmc.org/ -->
<strings>
+ <string id="30002">ファンアートを有効に</string>
+ <string id="30003">ポスターを使用</string>
<string id="30004">言語</string>
</strings>
--- /dev/null
+<?xml version="1.0" encoding="utf-8" standalone="yes"?>
+<!-- Translated using Transifex web application. For support, or if you would like to to help out, please visit your language team! -->
+<!-- Malayalam language-Team URL: http://www.transifex.com/projects/p/XBMC-Main-Frodo/language/ml/ -->
+<!-- Report language file syntax bugs at: http://trac.xbmc.org/ -->
+
+<strings>
+ <string id="30004">ഭാഷ</string>
+</strings>
<!-- Report language file syntax bugs at: http://trac.xbmc.org/ -->
<strings>
- <string id="30000">Usar Ordem DVD</string>
- <string id="30001">Usar Ordenamento Absoluto (Temporada Única)</string>
+ <string id="30000">Usar a ordem do DVD</string>
+ <string id="30001">Usar ordenamento absoluto (Temporada Única)</string>
<string id="30002">Ativar Fanart</string>
- <string id="30003">Preferir Posters</string>
+ <string id="30003">Preferir Cartazes</string>
<string id="30004">Idioma</string>
</strings>
<string id="30000">Usar ordem do DVD</string>
<string id="30001">Usar Ordem Absoluta (Temporada única)</string>
<string id="30002">Activar Fanart</string>
- <string id="30003">Preferir pósters</string>
+ <string id="30003">Preferir posters</string>
<string id="30004">Idioma</string>
</strings>
<expression><EpisodeNumber>([0-9]*)</EpisodeNumber>.*?<SeasonNumber>0</SeasonNumber></expression>
</RegExp>
<!-- Normal Season/Episodes -->
- <RegExp conditional="!absolutenumber" input="$$8" output="<season>\1</season>" dest="4+">
- <expression><SeasonNumber>([^<]*)</SeasonNumber></expression>
+ <RegExp conditional="!absolutenumber" input="$$9" output="<season>\1</season>" dest="4+">
+ <RegExp conditional="!dvdorder" input="$$8" output="\1" dest="9">
+ <expression clear="yes"><SeasonNumber>([^<]*)</SeasonNumber></expression>
+ </RegExp>
+ <RegExp conditional="dvdorder" input="$$8" output="\1" dest="9">
+ <expression><Combined_season>([^<]*)</Combined_season></expression>
+ </RegExp>
+ <expression/>
</RegExp>
- <RegExp conditional="!absolutenumber" input="$$8" output="<episode>\1</episode>" dest="4+">
- <expression><EpisodeNumber>([^<]*)</EpisodeNumber></expression>
+ <RegExp conditional="!absolutenumber" input="$$9" output="<episode>\1</episode>" dest="4+">
+ <RegExp conditional="!dvdorder" input="$$8" output="\1" dest="9">
+ <expression clear="yes"><EpisodeNumber>([^<]*)</EpisodeNumber></expression>
+ </RegExp>
+ <RegExp conditional="dvdorder" input="$$8" output="\1" dest="9">
+ <expression><Combined_episodenumber>([^<]*)</Combined_episodenumber></expression>
+ </RegExp>
+ <expression/>
</RegExp>
<RegExp input="$$8" output="<thumb>http://thetvdb.com/banners/\1</thumb>" dest="4+">
<expression><filename>([^<]+)</filename></expression>
<control type="group">
<posx>720</posx>
<posy>0</posy>
- <animation effect="slide" end="-400,0" time="200" condition="Window.IsVisible(133)">conditional</animation>
<animation effect="slide" end="0,-80" time="200" condition="Window.IsVisible(FullscreenVideo) | Window.IsVisible(Visualisation)">conditional</animation>
<control type="image">
<posx>0</posx>
</control>
</control>
</controls>
-</window>
\ No newline at end of file
+</window>
</control>
</control>
</control>
- <include>SideBladeRight</include>
<include>Clock</include>
<control type="label" id="20">
<visible>false</visible>
</control>
</controls>
-</window>
\ No newline at end of file
+</window>
</control>
</control>
</control>
- <include>SideBladeRight</include>
</controls>
-</window>
\ No newline at end of file
+</window>
-Subproject commit 1d03746646111f97e28b807a290bd762c7db547c
+Subproject commit 932a728725889c4220bc4ac42f18b082e4361f86
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-<addon id="weather.wunderground" name="Weather Underground" version="1.0.8" provider-name="Team XBMC">
+<addon id="weather.wunderground" name="Weather Underground" version="1.0.9" provider-name="Team XBMC">
<requires>
<import addon="xbmc.python" version="2.1.0"/>
<import addon="script.module.simplejson" version="2.0.10"/>
<summary lang="no">Værvarsel fra wunderground.com</summary>
<summary lang="pl">Prognoza pogody ze strony wunderground.com</summary>
<summary lang="pt">Previsão meteorológica a partir de wunderground.com</summary>
- <summary lang="pt_BR">Previsão Climática por wunderground.com </summary>
+ <summary lang="pt_BR">Previsão do tempo por wunderground.com</summary>
<summary lang="ro">Starea vremii de la wunderground.com</summary>
<summary lang="ru">Прогноз погоды с сайта wunderground.com</summary>
<summary lang="sk">Predpoveď počasia z wunderground.com</summary>
<summary lang="sl">Vremenska napoved, ki jo zagotavlja wunderground.com</summary>
<summary lang="sv">Väderprognos från wunderground.com</summary>
<summary lang="th">พยากรณ์อากาศจาก wunderground.com</summary>
+ <summary lang="tr">Hava durumu wunderground.com sitesinden alınır</summary>
<summary lang="uk">Прогноз погоди з wunderground.com</summary>
<summary lang="zh">来自wunderground.com的天气预报</summary>
<summary lang="zh_TW">weatherground.com 上的天氣預報</summary>
<description lang="no">Værvarsel fra Weather Underground (http://www.wunderground.com)</description>
<description lang="pl">Prognoza pogody jest dostarczona dzięki uprzejmości serwisu: (http://www.wunderground.com/)</description>
<description lang="pt">Previsão meteorológica fornecida por Weather Underground (http://www.wunderground.com/)</description>
- <description lang="pt_BR">Previsão do clima por Weather Underground (http://www.wunderground.com/)</description>
+ <description lang="pt_BR">Previsão do tempo por Weather Underground (http://www.wunderground.com/)</description>
<description lang="ro">Starea vremii furnizată de Weather Underground (http://www.wunderground.com/)</description>
<description lang="ru">Прогноз погоды предоставлен компанией Weather Underground (http://www.wunderground.com/)</description>
<description lang="sk">Predpoveď počasia poskytovaná od Weather Underground (http://www.wunderground.com/)</description>
<description lang="sl">Vremenska napoved, ki jo zagotavlja Weather Underground (http://www.wunderground.com/)</description>
<description lang="sv">Väderprognos tillhandahållen av Weather Underground (http://www.wunderground.com/)</description>
<description lang="th">พยากรณ์อากาศโดย Weather Underground (http://www.wunderground.com/)</description>
+ <description lang="tr">Hava Durumu Weather Underground tarafından sağlanır (http://www.wunderground.com/)</description>
<description lang="uk">Прогноз погоди надано Weather Underground (http://www.wunderground.com/)</description>
<description lang="zh">由Weather Underground(http://www.wunderground.com/)提供的天气预报</description>
<description lang="zh_TW">天氣預報由 Weather Underground (http://www.wunderground.com/) 所提供</description>
<disclaimer lang="no">Bruken av dette tillegget betinger at du har godkjent brukervilkårene på http://www.wunderground.com/weather/api/d/terms.html</disclaimer>
<disclaimer lang="pl">Korzystanie z tego dodatku jest równoważne z akceptacją regulaminu dostępnego pod adresem: http://www.wunderground.com/weather/api/d/terms.html</disclaimer>
<disclaimer lang="pt">O uso deste Add-on implica o seu acordo com os Termos do Serviço. Pode encontrá-los em http://www.wunderground.com/weather/api/d/terms.html</disclaimer>
- <disclaimer lang="pt_BR">Usar este add-on implica que você concorda com os termos de uso que podem ser visualizados em http://www.wunderground.com/weather/api/d/terms.html</disclaimer>
+ <disclaimer lang="pt_BR">Usar este add-on implica que você concorda com os termos de uso localizados em http://www.wunderground.com/weather/api/d/terms.html</disclaimer>
<disclaimer lang="ro">Folosirea acestui plugin implică acceptarea Termenilor și Serviciilor aflate la adresa http://www.wunderground.com/weather/api/d/terms.html</disclaimer>
<disclaimer lang="ru">Используя это дополнение, вы соглашаетесь с условиями предоставления услуг, доступными по адресу http://www.wunderground.com/weather/api/d/terms.html</disclaimer>
- <disclaimer lang="sk">Používanie tohto add-onu znamená, že súhlasite s podmienkami používania služby umiestnenými na http://www.wunderground.com/weather/api/d/terms.html</disclaimer>
+ <disclaimer lang="sk">Používaním tohto doplnku súhlasíte s podmienkami používania služby umiestnenými na http://www.wunderground.com/weather/api/d/terms.html</disclaimer>
<disclaimer lang="sl">Z uporabo vtičnika se strinjate s Pogoji uporabe, ki se nahajajo na http://www.wunderground.com/weather/api/d/terms.html</disclaimer>
<disclaimer lang="sv">Användning av detta tillägg innebär att ni har accepterat användarvillkoren som går att hitta på http://www.wunderground.com/weather/api/d/terms.html</disclaimer>
<disclaimer lang="th">ในการใช้บริการโปรแกรมเสริมนี้หมายความว่าคุณได้ยอมรับเงื่อนไขในการให้บริการโดย http://www.wunderground.com/weather/api/d/terms.html</disclaimer>
+v1.0.9
+- updated language files from Transifex
+
v1.0.8
- language update
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
--- /dev/null
+# XBMC Media Center language file
+# Addon Name: Weather Underground
+# Addon id: weather.wunderground
+# Addon Provider: Team XBMC
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Main Translation Project (Frodo)\n"
+"Report-Msgid-Bugs-To: http://trac.xbmc.org/\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: XBMC Translation Team\n"
+"Language-Team: Amharic (http://www.transifex.com/projects/p/XBMC-Main-Frodo/language/am/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: am\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+msgctxt "#32101"
+msgid "Location setup"
+msgstr "አካባቢ ማሰናጃ "
+
+msgctxt "#32102"
+msgid "Advanced options"
+msgstr "የረቀቁ ምርጫዎች"
+
+msgctxt "#32111"
+msgid "Location 1"
+msgstr "አካባቢ 1 "
+
+msgctxt "#32112"
+msgid "Location 2"
+msgstr "አካባቢ 2 "
+
+msgctxt "#32113"
+msgid "Location 3"
+msgstr "አካባቢ 3 "
+
+msgctxt "#32114"
+msgid "Location 1 display name"
+msgstr "አካባቢ 1 ስም ማሳያ "
+
+msgctxt "#32115"
+msgid "Location 2 display name"
+msgstr "አካባቢ 2 ስም ማሳያ"
+
+msgctxt "#32116"
+msgid "Location 3 display name"
+msgstr "አካባቢ 3 ስም ማሳያ"
+
+msgctxt "#32117"
+msgid "Location 1 id"
+msgstr "አካባቢ 1 መለያ "
+
+msgctxt "#32118"
+msgid "Location 2 id"
+msgstr "አካባቢ 2 መለያ"
+
+msgctxt "#32119"
+msgid "Location 3 id"
+msgstr "አካባቢ 3 መለያ"
+
+msgctxt "#32121"
+msgid "Weekend"
+msgstr "የሳምንቱ መጨረሻ"
+
+msgctxt "#32122"
+msgid "Saturday/Sunday"
+msgstr "ቅዳሜ / እሑድ "
+
+msgctxt "#32123"
+msgid "Friday/Saturday"
+msgstr "አርብ / ቅዳሜ "
+
+msgctxt "#32124"
+msgid "Thursday/Friday"
+msgstr "ሐሙስ / አርብ "
+
+msgctxt "#32501"
+msgid "New Moon"
+msgstr "አዲስ ጨረቃ "
+
+msgctxt "#32505"
+msgid "Full Moon"
+msgstr "ሙሉ ጨረቃ "
+
+msgctxt "#32510"
+msgid "Warning"
+msgstr "ማስጠንቀቂያ "
+
+msgctxt "#32515"
+msgid "Forecast"
+msgstr "ግምት "
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
msgid "Warning"
msgstr "警告"
+msgctxt "#32511"
+msgid "Watch"
+msgstr "觀看"
+
+msgctxt "#32512"
+msgid "Advisory"
+msgstr "諮詢"
+
+msgctxt "#32513"
+msgid "Statement"
+msgstr "聲明"
+
msgctxt "#32514"
msgid "Outlook"
msgstr "氣象趨勢"
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
"Language: hr\n"
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
+msgctxt "#32101"
+msgid "Location setup"
+msgstr "Namjesti lokaciju"
+
+msgctxt "#32102"
+msgid "Advanced options"
+msgstr "Dodatne opcije"
+
+msgctxt "#32111"
+msgid "Location 1"
+msgstr "Lokacija 1"
+
+msgctxt "#32112"
+msgid "Location 2"
+msgstr "Lokacija 2"
+
+msgctxt "#32113"
+msgid "Location 3"
+msgstr "Lokacija 3"
+
+msgctxt "#32114"
+msgid "Location 1 display name"
+msgstr "Lokacija 1 ime ekrana"
+
+msgctxt "#32115"
+msgid "Location 2 display name"
+msgstr "Lokacija 2 ime ekrana"
+
+msgctxt "#32116"
+msgid "Location 3 display name"
+msgstr "Lokacija 3 ime ekrana"
+
+msgctxt "#32117"
+msgid "Location 1 id"
+msgstr "Lokacija 1 id"
+
+msgctxt "#32118"
+msgid "Location 2 id"
+msgstr "Lokacija 2 id"
+
+msgctxt "#32119"
+msgid "Location 3 id"
+msgstr "Lokacija 3 id"
+
+msgctxt "#32120"
+msgid "Enable logging"
+msgstr "Omogućiti prijave"
+
msgctxt "#32121"
msgid "Weekend"
msgstr "Vikend"
+msgctxt "#32122"
+msgid "Saturday/Sunday"
+msgstr "Subota/Nedjelja"
+
+msgctxt "#32123"
+msgid "Friday/Saturday"
+msgstr "Petak/Subota"
+
+msgctxt "#32124"
+msgid "Thursday/Friday"
+msgstr "Četvrtak/Petak"
+
+msgctxt "#32125"
+msgid "Map zoom level"
+msgstr "Razina zumiranja karte"
+
+msgctxt "#32126"
+msgid "Animated map"
+msgstr "Animirane mape"
+
+msgctxt "#32501"
+msgid "New Moon"
+msgstr "Mladi Mjesec"
+
+msgctxt "#32502"
+msgid "Waxing Crescent"
+msgstr "Prva četvrt"
+
+msgctxt "#32503"
+msgid "First Quarter"
+msgstr "Prva četvrtina"
+
+msgctxt "#32504"
+msgid "Waxing Gibbous"
+msgstr "Prva četvrt"
+
+msgctxt "#32505"
+msgid "Full Moon"
+msgstr "Puni Mjesec"
+
+msgctxt "#32506"
+msgid "Waning Gibbous"
+msgstr "Zadnja četvrt"
+
+msgctxt "#32507"
+msgid "Last Quarter"
+msgstr "Posljednja četvrtina"
+
+msgctxt "#32508"
+msgid "Waning Crescent"
+msgstr "Mlađak"
+
+msgctxt "#32510"
+msgid "Warning"
+msgstr "Obavjesti"
+
+msgctxt "#32511"
+msgid "Watch"
+msgstr "Bdjeti"
+
+msgctxt "#32512"
+msgid "Advisory"
+msgstr "Savjetnik"
+
+msgctxt "#32513"
+msgid "Statement"
+msgstr "Izvještaj"
+
msgctxt "#32514"
msgid "Outlook"
msgstr "Izgledi"
msgctxt "#32515"
msgid "Forecast"
msgstr "Prognoza"
+
+msgctxt "#32516"
+msgid "Synopsis"
+msgstr "Kratki sadržaj"
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
"Language: et\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+msgctxt "#32101"
+msgid "Location setup"
+msgstr "Asukoha sätted"
+
+msgctxt "#32102"
+msgid "Advanced options"
+msgstr "Täpsemad seaded"
+
+msgctxt "#32111"
+msgid "Location 1"
+msgstr "Asukoht 1"
+
+msgctxt "#32112"
+msgid "Location 2"
+msgstr "Asukoht 2"
+
+msgctxt "#32113"
+msgid "Location 3"
+msgstr "Asukoht 3"
+
+msgctxt "#32114"
+msgid "Location 1 display name"
+msgstr "Asukoht 1 kuvatav nimi"
+
+msgctxt "#32115"
+msgid "Location 2 display name"
+msgstr "Asukoht 2 kuvatav nimi"
+
+msgctxt "#32116"
+msgid "Location 3 display name"
+msgstr "Asukoht 3 kuvatav nimi"
+
+msgctxt "#32117"
+msgid "Location 1 id"
+msgstr "Asukoht 1 id"
+
+msgctxt "#32118"
+msgid "Location 2 id"
+msgstr "Asukoht 2 id"
+
+msgctxt "#32119"
+msgid "Location 3 id"
+msgstr "Asukoht 3 id"
+
+msgctxt "#32120"
+msgid "Enable logging"
+msgstr "Luba logimine"
+
msgctxt "#32121"
msgid "Weekend"
msgstr "Nädalavahetus"
+msgctxt "#32122"
+msgid "Saturday/Sunday"
+msgstr "Laupäev/Pühapäev"
+
+msgctxt "#32123"
+msgid "Friday/Saturday"
+msgstr "Reede/Laupäev"
+
+msgctxt "#32124"
+msgid "Thursday/Friday"
+msgstr "Neljapäev/Reede"
+
+msgctxt "#32125"
+msgid "Map zoom level"
+msgstr "Kaardi suurenduse tase"
+
+msgctxt "#32126"
+msgid "Animated map"
+msgstr "Animeeritud kaart"
+
+msgctxt "#32501"
+msgid "New Moon"
+msgstr "Noorkuu"
+
+msgctxt "#32502"
+msgid "Waxing Crescent"
+msgstr "Viimane veerand"
+
+msgctxt "#32503"
+msgid "First Quarter"
+msgstr "Esimene veerand"
+
+msgctxt "#32504"
+msgid "Waxing Gibbous"
+msgstr "Kasvav kuu"
+
+msgctxt "#32505"
+msgid "Full Moon"
+msgstr "Täiskuu"
+
+msgctxt "#32506"
+msgid "Waning Gibbous"
+msgstr "Kahanev kuu"
+
+msgctxt "#32507"
+msgid "Last Quarter"
+msgstr "Viimane veerand"
+
+msgctxt "#32508"
+msgid "Waning Crescent"
+msgstr "Viimane veerand"
+
msgctxt "#32510"
msgid "Warning"
msgstr "Hoiatus"
+msgctxt "#32511"
+msgid "Watch"
+msgstr "Vaata"
+
+msgctxt "#32512"
+msgid "Advisory"
+msgstr "Nõuandev"
+
+msgctxt "#32513"
+msgid "Statement"
+msgstr "Avaldus"
+
msgctxt "#32514"
msgid "Outlook"
msgstr "Väljavaade"
msgctxt "#32515"
msgid "Forecast"
msgstr "Ilmateade"
+
+msgctxt "#32516"
+msgid "Synopsis"
+msgstr "Ülevaade"
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
"Language: gl\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+msgctxt "#32101"
+msgid "Location setup"
+msgstr "Configuración da localización"
+
+msgctxt "#32102"
+msgid "Advanced options"
+msgstr "Opcións Avanzadas"
+
+msgctxt "#32111"
+msgid "Location 1"
+msgstr "Localización 1"
+
+msgctxt "#32112"
+msgid "Location 2"
+msgstr "Localización 2"
+
+msgctxt "#32113"
+msgid "Location 3"
+msgstr "Localización 3"
+
+msgctxt "#32114"
+msgid "Location 1 display name"
+msgstr "Nome a mostrar da localización 1"
+
+msgctxt "#32115"
+msgid "Location 2 display name"
+msgstr "Nome a mostrar da localización 2"
+
+msgctxt "#32116"
+msgid "Location 3 display name"
+msgstr "Nome a mostrar da localización 3"
+
+msgctxt "#32117"
+msgid "Location 1 id"
+msgstr "ID da localización 1"
+
+msgctxt "#32118"
+msgid "Location 2 id"
+msgstr "ID da localización 2"
+
+msgctxt "#32119"
+msgid "Location 3 id"
+msgstr "ID da localización 3"
+
+msgctxt "#32120"
+msgid "Enable logging"
+msgstr "Habilitar o rexistro"
+
msgctxt "#32121"
msgid "Weekend"
msgstr "Fin de semana"
+msgctxt "#32122"
+msgid "Saturday/Sunday"
+msgstr "Sábado/Domingo"
+
+msgctxt "#32123"
+msgid "Friday/Saturday"
+msgstr "Venres/Sábado"
+
+msgctxt "#32124"
+msgid "Thursday/Friday"
+msgstr "Xoves/Venres"
+
+msgctxt "#32125"
+msgid "Map zoom level"
+msgstr "Nivel de aumento do mapa"
+
+msgctxt "#32126"
+msgid "Animated map"
+msgstr "Mapa animado"
+
+msgctxt "#32501"
+msgid "New Moon"
+msgstr "Lúa Nova"
+
+msgctxt "#32502"
+msgid "Waxing Crescent"
+msgstr "Lúa Nova Visible"
+
+msgctxt "#32503"
+msgid "First Quarter"
+msgstr "Cuarto Crecente"
+
+msgctxt "#32504"
+msgid "Waxing Gibbous"
+msgstr "Gibosa Crecente"
+
+msgctxt "#32505"
+msgid "Full Moon"
+msgstr "Lúa Chea"
+
+msgctxt "#32506"
+msgid "Waning Gibbous"
+msgstr "Gibosa Minguante"
+
+msgctxt "#32507"
+msgid "Last Quarter"
+msgstr "Cuarto Minguante"
+
+msgctxt "#32508"
+msgid "Waning Crescent"
+msgstr "Lúa Minguante"
+
msgctxt "#32510"
msgid "Warning"
msgstr "Atención"
+msgctxt "#32511"
+msgid "Watch"
+msgstr "Vixiar"
+
+msgctxt "#32512"
+msgid "Advisory"
+msgstr "Consello"
+
+msgctxt "#32513"
+msgid "Statement"
+msgstr "Declaración"
+
msgctxt "#32514"
msgid "Outlook"
msgstr "Perspectiva"
msgctxt "#32515"
msgid "Forecast"
msgstr "Predición"
+
+msgctxt "#32516"
+msgid "Synopsis"
+msgstr "Sinopse"
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
msgctxt "#32503"
msgid "First Quarter"
-msgstr "반달"
-
-msgctxt "#32504"
-msgid "Waxing Gibbous"
msgstr "상현달"
msgctxt "#32505"
msgid "Full Moon"
msgstr "보름달"
-msgctxt "#32506"
-msgid "Waning Gibbous"
-msgstr "하현달"
-
msgctxt "#32507"
msgid "Last Quarter"
-msgstr "반달"
+msgstr "하현달"
msgctxt "#32508"
msgid "Waning Crescent"
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
"Language: no\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+msgctxt "#32101"
+msgid "Location setup"
+msgstr "Steder"
+
+msgctxt "#32102"
+msgid "Advanced options"
+msgstr "Avanserte alternativer"
+
+msgctxt "#32111"
+msgid "Location 1"
+msgstr "Sted 1"
+
+msgctxt "#32112"
+msgid "Location 2"
+msgstr "Sted 2"
+
+msgctxt "#32113"
+msgid "Location 3"
+msgstr "Sted 3"
+
+msgctxt "#32114"
+msgid "Location 1 display name"
+msgstr "Visningsnavn for sted 1"
+
+msgctxt "#32115"
+msgid "Location 2 display name"
+msgstr "Visningsnavn for sted 2"
+
+msgctxt "#32116"
+msgid "Location 3 display name"
+msgstr "Visningsnavn for sted 3"
+
+msgctxt "#32117"
+msgid "Location 1 id"
+msgstr "ID for sted 1"
+
+msgctxt "#32118"
+msgid "Location 2 id"
+msgstr "ID for sted 2"
+
+msgctxt "#32119"
+msgid "Location 3 id"
+msgstr "ID for sted 3"
+
+msgctxt "#32120"
+msgid "Enable logging"
+msgstr "Aktiver logging"
+
msgctxt "#32121"
msgid "Weekend"
msgstr "Helg"
+msgctxt "#32122"
+msgid "Saturday/Sunday"
+msgstr "Lørdag/søndag"
+
+msgctxt "#32123"
+msgid "Friday/Saturday"
+msgstr "Fredag/lørdag"
+
+msgctxt "#32124"
+msgid "Thursday/Friday"
+msgstr "Torsdag/fredag"
+
+msgctxt "#32125"
+msgid "Map zoom level"
+msgstr "Zoomnivå for kart"
+
+msgctxt "#32126"
+msgid "Animated map"
+msgstr "Animert kart"
+
+msgctxt "#32501"
+msgid "New Moon"
+msgstr "Nymåne"
+
+msgctxt "#32502"
+msgid "Waxing Crescent"
+msgstr "Waxing Crescent"
+
+msgctxt "#32503"
+msgid "First Quarter"
+msgstr "Halvmåne ny"
+
+msgctxt "#32504"
+msgid "Waxing Gibbous"
+msgstr "Tiltagende mot fullmåne"
+
+msgctxt "#32505"
+msgid "Full Moon"
+msgstr "Fullmåne"
+
+msgctxt "#32506"
+msgid "Waning Gibbous"
+msgstr "Minkende fra fullmåne"
+
+msgctxt "#32507"
+msgid "Last Quarter"
+msgstr "Halvmåne ne"
+
+msgctxt "#32508"
+msgid "Waning Crescent"
+msgstr "Minkende fra fullmåne"
+
msgctxt "#32510"
msgid "Warning"
msgstr "Advarsel"
+msgctxt "#32511"
+msgid "Watch"
+msgstr "Se"
+
+msgctxt "#32512"
+msgid "Advisory"
+msgstr "Anbefaling"
+
+msgctxt "#32513"
+msgid "Statement"
+msgstr "Uttalelse"
+
msgctxt "#32514"
msgid "Outlook"
-msgstr "Outlook"
+msgstr "Prognose"
msgctxt "#32515"
msgid "Forecast"
msgstr "Værvarsel"
+
+msgctxt "#32516"
+msgid "Synopsis"
+msgstr "Sammendrag"
--- /dev/null
+# XBMC Media Center language file
+# Addon Name: Weather Underground
+# Addon id: weather.wunderground
+# Addon Provider: Team XBMC
+msgid ""
+msgstr ""
+"Project-Id-Version: XBMC Main Translation Project (Frodo)\n"
+"Report-Msgid-Bugs-To: http://trac.xbmc.org/\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: XBMC Translation Team\n"
+"Language-Team: Persian (http://www.transifex.com/projects/p/XBMC-Main-Frodo/language/fa/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: fa\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+msgctxt "#32111"
+msgid "Location 1"
+msgstr "موقعیت 1"
+
+msgctxt "#32112"
+msgid "Location 2"
+msgstr "موقعیت 2"
+
+msgctxt "#32113"
+msgid "Location 3"
+msgstr "موقعیت 3"
+
+msgctxt "#32114"
+msgid "Location 1 display name"
+msgstr "نام مستعار موقیت 1"
+
+msgctxt "#32115"
+msgid "Location 2 display name"
+msgstr "نام مستعار موقعیت 2"
+
+msgctxt "#32116"
+msgid "Location 3 display name"
+msgstr "نام مستعار موقعیت 3"
+
+msgctxt "#32121"
+msgid "Weekend"
+msgstr "آخر هفته"
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
msgctxt "#32111"
msgid "Location 1"
-msgstr "Cidade 1"
+msgstr "Local 1"
msgctxt "#32112"
msgid "Location 2"
-msgstr "Cidade 2"
+msgstr "Local 2"
msgctxt "#32113"
msgid "Location 3"
-msgstr "Cidade 3"
+msgstr "Local 3"
msgctxt "#32114"
msgid "Location 1 display name"
-msgstr "Nome da Cidade"
+msgstr "Nome de exibição do local 1"
msgctxt "#32115"
msgid "Location 2 display name"
-msgstr "Nome da Cidade"
+msgstr "Nome de exibição do local 2"
msgctxt "#32116"
msgid "Location 3 display name"
-msgstr "Nome da Cidade"
+msgstr "Nome de exibição do local 3"
msgctxt "#32117"
msgid "Location 1 id"
-msgstr "Local 1 id"
+msgstr "ID do local 1"
msgctxt "#32118"
msgid "Location 2 id"
-msgstr "Local 2 id"
+msgstr "ID do local 2"
msgctxt "#32119"
msgid "Location 3 id"
-msgstr "Local 3 id"
+msgstr "ID do local 3"
msgctxt "#32120"
msgid "Enable logging"
-msgstr "Ativar logando"
+msgstr "Ativar registro"
msgctxt "#32121"
msgid "Weekend"
msgctxt "#32124"
msgid "Thursday/Friday"
-msgstr "Quarta/Quinta"
+msgstr "Quinta/Sexta"
msgctxt "#32125"
msgid "Map zoom level"
msgctxt "#32126"
msgid "Animated map"
-msgstr "Mapa Animado"
+msgstr "Mapa animado"
msgctxt "#32501"
msgid "New Moon"
msgctxt "#32502"
msgid "Waxing Crescent"
-msgstr "Quarta Crescente"
+msgstr "Lua Crescente"
msgctxt "#32503"
msgid "First Quarter"
-msgstr "Lua Nova"
+msgstr "Quarto Crescente"
msgctxt "#32504"
msgid "Waxing Gibbous"
-msgstr "Quarta Minguante"
+msgstr "Lua Gibosa"
msgctxt "#32505"
msgid "Full Moon"
msgctxt "#32506"
msgid "Waning Gibbous"
-msgstr "Minguante"
+msgstr "Lua Balsâmica"
msgctxt "#32507"
msgid "Last Quarter"
-msgstr "Lua Minguante"
+msgstr "Quarto Minguante"
msgctxt "#32508"
msgid "Waning Crescent"
-msgstr "Lua Crescente"
+msgstr "Lua Minguante"
msgctxt "#32510"
msgid "Warning"
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
"Language: sk\n"
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
+msgctxt "#32101"
+msgid "Location setup"
+msgstr "Nastavenie Polohy"
+
+msgctxt "#32102"
+msgid "Advanced options"
+msgstr "Pokročilé nastavenia"
+
+msgctxt "#32111"
+msgid "Location 1"
+msgstr "Poloha 1"
+
+msgctxt "#32112"
+msgid "Location 2"
+msgstr "Poloha 2"
+
+msgctxt "#32113"
+msgid "Location 3"
+msgstr "Poloha 3"
+
+msgctxt "#32114"
+msgid "Location 1 display name"
+msgstr "Pomenovanie Polohy 1"
+
+msgctxt "#32115"
+msgid "Location 2 display name"
+msgstr "Pomenovanie Polohy 2"
+
+msgctxt "#32116"
+msgid "Location 3 display name"
+msgstr "Pomenovanie Polohy 3"
+
+msgctxt "#32117"
+msgid "Location 1 id"
+msgstr "ID Polohy 1"
+
+msgctxt "#32118"
+msgid "Location 2 id"
+msgstr "ID Polohy 2"
+
+msgctxt "#32119"
+msgid "Location 3 id"
+msgstr "ID Polohy 3"
+
+msgctxt "#32120"
+msgid "Enable logging"
+msgstr "Povoliť logovanie"
+
msgctxt "#32121"
msgid "Weekend"
msgstr "Víkend"
+msgctxt "#32122"
+msgid "Saturday/Sunday"
+msgstr "Sobota/Nedeľa"
+
+msgctxt "#32123"
+msgid "Friday/Saturday"
+msgstr "Piatok/Sobota"
+
+msgctxt "#32124"
+msgid "Thursday/Friday"
+msgstr "Štvrtok/Piatok"
+
+msgctxt "#32125"
+msgid "Map zoom level"
+msgstr "Úroveň priblíženia mapy"
+
+msgctxt "#32126"
+msgid "Animated map"
+msgstr "Animovaná mapa"
+
+msgctxt "#32501"
+msgid "New Moon"
+msgstr "Nov mesiaca"
+
+msgctxt "#32502"
+msgid "Waxing Crescent"
+msgstr "Dorastajúci kosák"
+
+msgctxt "#32503"
+msgid "First Quarter"
+msgstr "Prvá štvrť"
+
+msgctxt "#32504"
+msgid "Waxing Gibbous"
+msgstr "Dorastajúci mesiac"
+
+msgctxt "#32505"
+msgid "Full Moon"
+msgstr "Spln mesiaca"
+
+msgctxt "#32506"
+msgid "Waning Gibbous"
+msgstr "Cúvajúci mesiac"
+
+msgctxt "#32507"
+msgid "Last Quarter"
+msgstr "Posledná štvrť"
+
+msgctxt "#32508"
+msgid "Waning Crescent"
+msgstr "Ubúdajúci kosák"
+
msgctxt "#32510"
msgid "Warning"
msgstr "Upozornenie"
+msgctxt "#32511"
+msgid "Watch"
+msgstr "Výstraha"
+
+msgctxt "#32512"
+msgid "Advisory"
+msgstr "Informačné spravodajstvo"
+
+msgctxt "#32513"
+msgid "Statement"
+msgstr "Oznámenie"
+
msgctxt "#32514"
msgid "Outlook"
msgstr "Výhľad"
msgctxt "#32515"
msgid "Forecast"
msgstr "Predpoveď"
+
+msgctxt "#32516"
+msgid "Synopsis"
+msgstr "Zhrnutie"
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
"Language: es_MX\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+msgctxt "#32101"
+msgid "Location setup"
+msgstr "Configurar Ubicación"
+
+msgctxt "#32102"
+msgid "Advanced options"
+msgstr "Opciones Avanzadas"
+
+msgctxt "#32111"
+msgid "Location 1"
+msgstr "Ubicación 1"
+
+msgctxt "#32112"
+msgid "Location 2"
+msgstr "Ubicación 2"
+
+msgctxt "#32113"
+msgid "Location 3"
+msgstr "Ubicación 3"
+
+msgctxt "#32114"
+msgid "Location 1 display name"
+msgstr "Ubicación 1 Mostrar Nombre"
+
+msgctxt "#32115"
+msgid "Location 2 display name"
+msgstr "Ubicación 2 Mostrar Nombre"
+
+msgctxt "#32116"
+msgid "Location 3 display name"
+msgstr "Ubicación 3 Mostrar Nombre"
+
+msgctxt "#32117"
+msgid "Location 1 id"
+msgstr "Ubicación 1 id"
+
+msgctxt "#32118"
+msgid "Location 2 id"
+msgstr "Ubicación 2 id"
+
+msgctxt "#32119"
+msgid "Location 3 id"
+msgstr "Ubicación 3 id"
+
+msgctxt "#32120"
+msgid "Enable logging"
+msgstr "Habilitar logs"
+
msgctxt "#32121"
msgid "Weekend"
msgstr "Fin de semana"
+msgctxt "#32122"
+msgid "Saturday/Sunday"
+msgstr "Sábado/Domingo"
+
+msgctxt "#32123"
+msgid "Friday/Saturday"
+msgstr "Viernes/Sábado"
+
+msgctxt "#32124"
+msgid "Thursday/Friday"
+msgstr "Jueves/Viernes"
+
+msgctxt "#32125"
+msgid "Map zoom level"
+msgstr "Nivel de Acercamiento en Mapa"
+
+msgctxt "#32126"
+msgid "Animated map"
+msgstr "Mapa Animado"
+
+msgctxt "#32501"
+msgid "New Moon"
+msgstr "Luna nueva"
+
+msgctxt "#32502"
+msgid "Waxing Crescent"
+msgstr "Luna Creciente"
+
+msgctxt "#32503"
+msgid "First Quarter"
+msgstr "Primer Cuarto"
+
+msgctxt "#32504"
+msgid "Waxing Gibbous"
+msgstr "Gibosa Iluminarte"
+
+msgctxt "#32505"
+msgid "Full Moon"
+msgstr "Luna Llena"
+
+msgctxt "#32506"
+msgid "Waning Gibbous"
+msgstr "Luna Menguante"
+
+msgctxt "#32507"
+msgid "Last Quarter"
+msgstr "Último cuarto"
+
+msgctxt "#32508"
+msgid "Waning Crescent"
+msgstr "Creciente Menguante"
+
+msgctxt "#32510"
+msgid "Warning"
+msgstr "Alerta"
+
+msgctxt "#32511"
+msgid "Watch"
+msgstr "Reloj"
+
+msgctxt "#32512"
+msgid "Advisory"
+msgstr "Advertencia"
+
+msgctxt "#32513"
+msgid "Statement"
+msgstr "Sentencia"
+
msgctxt "#32514"
msgid "Outlook"
msgstr "Outlook"
msgctxt "#32515"
msgid "Forecast"
msgstr "Pronóstico"
+
+msgctxt "#32516"
+msgid "Synopsis"
+msgstr "Sinopsis"
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
msgid "Warning"
msgstr "Uyarı"
+msgctxt "#32511"
+msgid "Watch"
+msgstr "İzle"
+
msgctxt "#32514"
msgid "Outlook"
msgstr "Outlook"
# XBMC Media Center language file
# Addon Name: Weather Underground
# Addon id: weather.wunderground
-# Addon version: 1.0.8
# Addon Provider: Team XBMC
msgid ""
msgstr ""
test/Makefile \
tools/Makefile)
-AM_CONFIG_HEADER(config.h)
+AC_CONFIG_HEADERS(config.h)
AM_INIT_AUTOMAKE([1.8 gnits check-news dist-bzip2])
AM_ACLOCAL_INCLUDE(m4)
AM_MAINTAINER_MODE
AC_AIX
AC_ISC_POSIX
AC_PROG_LIBTOOL
-AM_PROG_CC_STDC
AM_PROG_CC_C_O
AC_PROG_INSTALL
AC_PROG_LN_S
}
NPT_Result result = PLT_MediaObject::FromDidl(entry);
-
- // make sure we have at least one valid resource
- if (m_Resources.GetItemCount() == 0) {
- NPT_CHECK_SEVERE(NPT_ERROR_INVALID_PARAMETERS);
- }
return result;
}
AC_CANONICAL_SYSTEM
AM_INIT_AUTOMAKE(libdvdcss, 1.2.10)
-AM_CONFIG_HEADER(config.h)
+AC_CONFIG_HEADERS(config.h)
AC_PROG_CC
AC_STDC_HEADERS
AM_INIT_AUTOMAKE
-AM_CONFIG_HEADER([config.h])
+AC_CONFIG_HEADERS([config.h])
dnl System type.
AC_PREREQ(2.57)
AC_INIT([libmicrohttpd], [0.4.5],[libmicrohttpd@gnu.org])
AM_INIT_AUTOMAKE([libmicrohttpd], [0.4.5])
-AM_CONFIG_HEADER([MHD_config.h])
+AC_CONFIG_HEADERS([MHD_config.h])
AC_CONFIG_MACRO_DIR([m4])
AH_TOP([#define _GNU_SOURCE 1])
SET TMP_DIR=%DEPS_DIR%\tmp
SET LIBNAME=xbmc-pvr-addons
-SET VERSION=5f97406cffb412ac5161c3dc51205caca009fcc7
+SET VERSION=96774c4f775b156a46fb58151379dece3e773c96
SET SOURCE=%LIBNAME%
SET GIT_URL=git://github.com/opdenkamp/%LIBNAME%.git
SET SOURCE_DIR=%TMP_DIR%\%SOURCE%
<backslash>ToggleFullScreen</backslash>
<home>FirstPage</home>
<end>LastPage</end>
+ <power>ActivateWindow(shutdownmenu)</power>
+ <sleep>ActivateWindow(shutdownmenu)</sleep>
<!-- PVR windows -->
<e>XBMC.ActivateWindowAndFocus(MyPVR, 31,0, 10,0)</e>
<h>XBMC.ActivateWindowAndFocus(MyPVR, 32,0, 11,0)</h>
<f4 mod="shift">ActivateWindow(music)</f4> <!-- Green -->
<f5 mod="shift">ActivateWindow(pictures)</f5> <!-- Yellow -->
<f6 mod="shift">ActivateWindow(programs)</f6> <!-- Blue -->
- <key id='61952'>ActivateWindow(shutdownmenu)</key> <!-- Power button -->
- <key id='285'>ContextMenu</key> <!-- User button -->
+ <f4>ContextMenu</f4> <!-- User button -->
</keyboard>
</global>
</keymap>
<setting key="standby_pc_on_tv_standby" type="enum" value="13011" label="36029" order="7" lvalues="36028|13005|13011" />
<setting key="standby_tv_on_pc_standby" type="bool" value="1" label="36026" order="8" />
<setting key="use_tv_menu_language" type="bool" value="1" label="36018" order="9" />
+ <setting key="physical_address" type="string" label="36021" value="0" order="10" />
<setting key="tv_vendor" type="int" value="0" configurable="0" />
<setting key="device_name" type="string" value="XBMC" configurable="0" />
<setting key="device_type" type="int" value="1" configurable="0" />
- <setting key="physical_address" type="string" label="36021" value="0" configurable="0" />
<setting key="cec_hdmi_port" type="int" value="1" label="36015" configurable="0" />
<setting key="connected_device" type="int" label="36019" value="0" configurable="0" />
<setting key="port" type="string" value="" label="36022" configurable="0" />
<rule name="rtv" protocols="rtv" player="DVDPlayer" />
<rule name="hdhomerun/myth/mms/udp" protocols="hdhomerun|myth|cmyth|mms|mmsh|udp" player="DVDPlayer" />
<rule name="lastfm/shout" protocols="lastfm|shout" player="PAPlayer" />
+ <rule name="rtmp" protocols="rtmp" player="videodefaultplayer" />
<!-- dvdplayer can play standard rtsp streams -->
<rule name="rtsp" protocols="rtsp" filetypes="!(rm|ra)" player="PAPlayer" />
# lib name, version
LIBNAME=rtmpdump
-VERSION=2.3
+VERSION=e0056c51cc1710c9a44d2a2c4e2f344fa9cabcf4
+GIT_DIR=$(TARBALLS_LOCATION)/$(LIBNAME).git
+BASE_URL=git://git.ffmpeg.org/$(LIBNAME).git
SOURCE=$(LIBNAME)-$(VERSION)
-ARCHIVE=$(SOURCE).tgz
+#tell git to use the addons repo rather than xbmc's repo
+export GIT_DIR
+export GIT_WORK_TREE=$(PLATFORM)
# configuration settings
all: .installed-$(PLATFORM)
-$(TARBALLS_LOCATION)/$(ARCHIVE):
- $(RETRIEVE_TOOL) $(RETRIEVE_TOOL_FLAGS) $(BASE_URL)/$(ARCHIVE)
+$(GIT_DIR)/HEAD:
+ cd $(TARBALLS_LOCATION); git clone --bare $(BASE_URL)
-$(PLATFORM): $(TARBALLS_LOCATION)/$(ARCHIVE) $(DEPS)
- rm -rf $(PLATFORM)/*; mkdir -p $(PLATFORM)
- $(ARCHIVE_TOOL) $(ARCHIVE_TOOL_FLAGS) $(TARBALLS_LOCATION)/$(ARCHIVE)
+$(GIT_DIR)/current/$(VERSION): $(GIT_DIR)/HEAD $(DEPS)
+ git rev-list -1 $(VERSION) >/dev/null || git fetch origin "+refs/heads/*:refs/remotes/origin/*"
+ git rev-list -1 $(VERSION) >/dev/null
+ rm -rf $(GIT_DIR)/current; mkdir -p $(GIT_DIR)/current
+ touch $@
+
+$(PLATFORM)/bootstrap: $(GIT_DIR)/current/$(VERSION)
+ rm -rf $(PLATFORM); mkdir -p $(PLATFORM)
+ git checkout $(VERSION) -- .
+
+$(PLATFORM): $(PLATFORM)/bootstrap
+ cd $(PLATFORM); patch -p1 < ../librtmp-60-second-fix.patch
cd $(PLATFORM); patch -p0 < ../prefix.patch
$(LIBDYLIB): $(PLATFORM)
- $(MAKE) -C $(PLATFORM)/librtmp CROSS_COMPILE=$(CROSSTOOLS) PREFIX=$(PREFIX)
+ $(MAKE) -C $(PLATFORM)/librtmp CROSS_COMPILE=$(CROSSTOOLS) PREFIX=$(PREFIX) XCFLAGS="$(CFLAGS)" XLDFLAGS="$(LDFLAGS) -lm"
.installed-$(PLATFORM): $(LIBDYLIB)
$(MAKE) -C $(PLATFORM)/librtmp install PREFIX=$(PREFIX)
--- /dev/null
+diff --git a/librtmp/amf.c b/librtmp/amf.c
+index ce84f81..a25bc04 100644
+--- a/librtmp/amf.c
++++ b/librtmp/amf.c
+@@ -610,6 +610,9 @@ AMFProp_Decode(AMFObjectProperty *prop, const char *pBuffer, int nSize,
+ return -1;
+ }
+
++ if (*pBuffer == AMF_NULL)
++ bDecodeName = 0;
++
+ if (bDecodeName && nSize < 4)
+ { /* at least name (length + at least 1 byte) and 1 byte of data */
+ RTMP_Log(RTMP_LOGDEBUG,
+@@ -801,8 +804,8 @@ AMFProp_Dump(AMFObjectProperty *prop)
+ }
+ else
+ {
+- name.av_val = "no-name.";
+- name.av_len = sizeof("no-name.") - 1;
++ name.av_val = "no-name";
++ name.av_len = sizeof("no-name") - 1;
+ }
+ if (name.av_len > 18)
+ name.av_len = 18;
+diff --git a/librtmp/dh.h b/librtmp/dh.h
+index 9959532..e29587b 100644
+--- a/librtmp/dh.h
++++ b/librtmp/dh.h
+@@ -61,7 +61,7 @@ static int MDH_generate_key(MDH *dh)
+ MP_set(&dh->ctx.P, dh->p);
+ MP_set(&dh->ctx.G, dh->g);
+ dh->ctx.len = 128;
+- dhm_make_public(&dh->ctx, 1024, out, 1, havege_rand, &RTMP_TLS_ctx->hs);
++ dhm_make_public(&dh->ctx, 1024, out, 1, havege_random, &RTMP_TLS_ctx->hs);
+ MP_new(dh->pub_key);
+ MP_new(dh->priv_key);
+ MP_set(dh->pub_key, &dh->ctx.GX);
+diff --git a/librtmp/handshake.h b/librtmp/handshake.h
+index 0438486..102ba82 100644
+--- a/librtmp/handshake.h
++++ b/librtmp/handshake.h
+@@ -965,8 +965,18 @@ HandShake(RTMP * r, int FP9HandShake)
+ __FUNCTION__);
+ RTMP_LogHex(RTMP_LOGDEBUG, reply, RTMP_SIG_SIZE);
+ #endif
+- if (!WriteN(r, (char *)reply, RTMP_SIG_SIZE))
+- return FALSE;
++ if (r->Link.CombineConnectPacket)
++ {
++ char *HandshakeResponse = malloc(RTMP_SIG_SIZE);
++ memcpy(HandshakeResponse, (char *) reply, RTMP_SIG_SIZE);
++ r->Link.HandshakeResponse.av_val = HandshakeResponse;
++ r->Link.HandshakeResponse.av_len = RTMP_SIG_SIZE;
++ }
++ else
++ {
++ if (!WriteN(r, (char *) reply, RTMP_SIG_SIZE))
++ return FALSE;
++ }
+
+ /* 2nd part of handshake */
+ if (ReadN(r, (char *)serversig, RTMP_SIG_SIZE) != RTMP_SIG_SIZE)
+diff --git a/librtmp/hashswf.c b/librtmp/hashswf.c
+index 9f4e2c0..eeed34c 100644
+--- a/librtmp/hashswf.c
++++ b/librtmp/hashswf.c
+@@ -70,7 +70,7 @@ extern TLS_CTX RTMP_TLS_ctx;
+
+ #endif /* CRYPTO */
+
+-#define AGENT "Mozilla/5.0"
++#define AGENT "Mozilla/5.0 (Windows NT 5.1; rv:8.0) Gecko/20100101 Firefox/8.0"
+
+ HTTPResult
+ HTTP_get(struct HTTP_ctx *http, const char *url, HTTP_read_callback *cb)
+@@ -528,7 +528,7 @@ RTMP_HashSWF(const char *url, unsigned int *size, unsigned char *hash,
+
+ if (strncmp(buf, "url: ", 5))
+ continue;
+- if (strncmp(buf + 5, url, hlen))
++ if (strncmp(buf + 5, url, strlen(buf + 5) - 1))
+ continue;
+ r1 = strrchr(buf, '/');
+ i = strlen(r1);
+diff --git a/librtmp/log.c b/librtmp/log.c
+index 0012985..856e3e4 100644
+--- a/librtmp/log.c
++++ b/librtmp/log.c
+@@ -52,8 +52,8 @@ static void rtmp_log_default(int level, const char *format, va_list vl)
+ vsnprintf(str, MAX_PRINT_LEN-1, format, vl);
+
+ /* Filter out 'no-name' */
+- if ( RTMP_debuglevel<RTMP_LOGALL && strstr(str, "no-name" ) != NULL )
+- return;
++ if (RTMP_debuglevel < RTMP_LOGDEBUG && strstr(str, "no-name") != NULL)
++ return;
+
+ if ( !fmsg ) fmsg = stderr;
+
+diff --git a/librtmp/rtmp.c b/librtmp/rtmp.c
+index 52d0254..bef37aa 100644
+--- a/librtmp/rtmp.c
++++ b/librtmp/rtmp.c
+@@ -27,6 +27,7 @@
+ #include <stdlib.h>
+ #include <string.h>
+ #include <assert.h>
++#include <math.h>
+
+ #include "rtmp_sys.h"
+ #include "log.h"
+@@ -45,6 +46,7 @@ TLS_CTX RTMP_TLS_ctx;
+
+ #define RTMP_SIG_SIZE 1536
+ #define RTMP_LARGE_HEADER_SIZE 12
++#define HEX2BIN(a) (((a)&0x40)?((a)&0xf)+9:((a)&0xf))
+
+ static const int packetSize[] = { 12, 8, 4, 1 };
+
+@@ -97,6 +99,9 @@ static int SendFCSubscribe(RTMP *r, AVal *subscribepath);
+ static int SendPlay(RTMP *r);
+ static int SendBytesReceived(RTMP *r);
+ static int SendUsherToken(RTMP *r, AVal *usherToken);
++static int SendInvoke(RTMP *r, AVal *Command, int queue);
++static int SendGetStreamLength(RTMP *r);
++static int strsplit(char *src, int srclen, char delim, char ***params);
+
+ #if 0 /* unused */
+ static int SendBGHasStream(RTMP *r, double dId, AVal *playpath);
+@@ -259,6 +264,8 @@ RTMP_Init(RTMP *r)
+ r->m_fVideoCodecs = 252.0;
+ r->Link.timeout = 30;
+ r->Link.swfAge = 30;
++ r->Link.CombineConnectPacket = TRUE;
++ r->Link.ConnectPacket = FALSE;
+ }
+
+ void
+@@ -337,6 +344,7 @@ RTMP_SetupStream(RTMP *r,
+ AVal *flashVer,
+ AVal *subscribepath,
+ AVal *usherToken,
++ AVal *WeebToken,
+ int dStart,
+ int dStop, int bLiveStream, long int timeout)
+ {
+@@ -359,6 +367,8 @@ RTMP_SetupStream(RTMP *r,
+ RTMP_Log(RTMP_LOGDEBUG, "subscribepath : %s", subscribepath->av_val);
+ if (usherToken && usherToken->av_val)
+ RTMP_Log(RTMP_LOGDEBUG, "NetStream.Authenticate.UsherToken : %s", usherToken->av_val);
++ if (WeebToken && WeebToken->av_val)
++ RTMP_Log(RTMP_LOGDEBUG, "WeebToken: %s", WeebToken->av_val);
+ if (flashVer && flashVer->av_val)
+ RTMP_Log(RTMP_LOGDEBUG, "flashVer : %s", flashVer->av_val);
+ if (dStart > 0)
+@@ -426,6 +436,8 @@ RTMP_SetupStream(RTMP *r,
+ r->Link.subscribepath = *subscribepath;
+ if (usherToken && usherToken->av_len)
+ r->Link.usherToken = *usherToken;
++ if (WeebToken && WeebToken->av_len)
++ r->Link.WeebToken = *WeebToken;
+ r->Link.seekTime = dStart;
+ r->Link.stopTime = dStop;
+ if (bLiveStream)
+@@ -483,14 +495,22 @@ static struct urlopt {
+ "Stream is live, no seeking possible" },
+ { AVC("subscribe"), OFF(Link.subscribepath), OPT_STR, 0,
+ "Stream to subscribe to" },
+- { AVC("jtv"), OFF(Link.usherToken), OPT_STR, 0,
+- "Justin.tv authentication token" },
+- { AVC("token"), OFF(Link.token), OPT_STR, 0,
++ { AVC("jtv"), OFF(Link.usherToken), OPT_STR, 0,
++ "Justin.tv authentication token"},
++ { AVC("weeb"), OFF(Link.WeebToken), OPT_STR, 0,
++ "Weeb.tv authentication token"},
++ { AVC("token"), OFF(Link.token), OPT_STR, 0,
+ "Key for SecureToken response" },
+ { AVC("swfVfy"), OFF(Link.lFlags), OPT_BOOL, RTMP_LF_SWFV,
+ "Perform SWF Verification" },
+ { AVC("swfAge"), OFF(Link.swfAge), OPT_INT, 0,
+ "Number of days to use cached SWF hash" },
++#ifdef CRYPTO
++ { AVC("swfsize"), OFF(Link.swfSize), OPT_INT, 0,
++ "Size of the decompressed SWF file"},
++ { AVC("swfhash"), OFF(Link.swfHash), OPT_STR, 0,
++ "SHA256 hash of the decompressed SWF file"},
++#endif
+ { AVC("start"), OFF(Link.seekTime), OPT_INT, 0,
+ "Stream start position in milliseconds" },
+ { AVC("stop"), OFF(Link.stopTime), OPT_INT, 0,
+@@ -751,9 +771,16 @@ int RTMP_SetupURL(RTMP *r, char *url)
+ }
+
+ #ifdef CRYPTO
+- if ((r->Link.lFlags & RTMP_LF_SWFV) && r->Link.swfUrl.av_len)
+- RTMP_HashSWF(r->Link.swfUrl.av_val, &r->Link.SWFSize,
+- (unsigned char *)r->Link.SWFHash, r->Link.swfAge);
++ RTMP_Log(RTMP_LOGDEBUG, "Khalsa: %d %d %s\n", r->Link.swfSize, r->Link.swfHash.av_len, r->Link.swfHash.av_val);
++ if (r->Link.swfSize && r->Link.swfHash.av_len)
++ {
++ int i, j = 0;
++ for (i = 0; i < r->Link.swfHash.av_len; i += 2)
++ r->Link.SWFHash[j++] = (HEX2BIN(r->Link.swfHash.av_val[i]) << 4) | HEX2BIN(r->Link.swfHash.av_val[i + 1]);
++ r->Link.SWFSize = (uint32_t) r->Link.swfSize;
++ }
++ else if ((r->Link.lFlags & RTMP_LF_SWFV) && r->Link.swfUrl.av_len)
++ RTMP_HashSWF(r->Link.swfUrl.av_val, &r->Link.SWFSize, (unsigned char *) r->Link.SWFHash, r->Link.swfAge);
+ #endif
+
+ if (r->Link.port == 0)
+@@ -854,6 +881,8 @@ RTMP_Connect0(RTMP *r, struct sockaddr * service)
+ }
+
+ setsockopt(r->m_sb.sb_socket, IPPROTO_TCP, TCP_NODELAY, (char *) &on, sizeof(on));
++ if (r->Link.protocol & RTMP_FEATURE_HTTP)
++ setsockopt(r->m_sb.sb_socket, SOL_SOCKET, SO_KEEPALIVE, (char *) &on, sizeof (on));
+
+ return TRUE;
+ }
+@@ -1308,8 +1337,24 @@ ReadN(RTMP *r, char *buffer, int n)
+ return 0;
+ }
+ }
+- if (r->m_resplen && !r->m_sb.sb_size)
+- RTMPSockBuf_Fill(&r->m_sb);
++
++ // Try to fill the whole buffer. previous buffer needs to be consumed
++ // completely before receiving new data.
++ if (r->m_resplen && (r->m_sb.sb_size <= 0))
++ {
++ do
++ {
++ nBytes = RTMPSockBuf_Fill(&r->m_sb);
++ if (nBytes == -1)
++ {
++ if (!r->m_sb.sb_timedout)
++ RTMP_Close(r);
++ return 0;
++ }
++ }
++ while (r->m_resplen && (r->m_sb.sb_size < r->m_resplen) && (nBytes > 0));
++ }
++
+ avail = r->m_sb.sb_size;
+ if (avail > r->m_resplen)
+ avail = r->m_resplen;
+@@ -1336,10 +1381,9 @@ ReadN(RTMP *r, char *buffer, int n)
+ r->m_sb.sb_size -= nRead;
+ nBytes = nRead;
+ r->m_nBytesIn += nRead;
+- if (r->m_bSendCounter
+- && r->m_nBytesIn > ( r->m_nBytesInSent + r->m_nClientBW / 10))
+- if (!SendBytesReceived(r))
+- return FALSE;
++ if (r->m_bSendCounter && r->m_nBytesIn > (r->m_nBytesInSent + r->m_nClientBW / 10))
++ if (!SendBytesReceived(r))
++ return FALSE;
+ }
+ /*RTMP_Log(RTMP_LOGDEBUG, "%s: %d bytes\n", __FUNCTION__, nBytes); */
+ #ifdef _DEBUG
+@@ -1390,6 +1434,16 @@ WriteN(RTMP *r, const char *buffer, int n)
+ }
+ #endif
+
++ if (r->Link.ConnectPacket)
++ {
++ char *ConnectPacket = malloc(r->Link.HandshakeResponse.av_len + n);
++ memcpy(ConnectPacket, r->Link.HandshakeResponse.av_val, r->Link.HandshakeResponse.av_len);
++ memcpy(ConnectPacket + r->Link.HandshakeResponse.av_len, ptr, n);
++ ptr = ConnectPacket;
++ n += r->Link.HandshakeResponse.av_len;
++ r->Link.ConnectPacket = FALSE;
++ }
++
+ while (n > 0)
+ {
+ int nBytes;
+@@ -1455,6 +1509,9 @@ SendConnectPacket(RTMP *r, RTMPPacket *cp)
+ char pbuf[4096], *pend = pbuf + sizeof(pbuf);
+ char *enc;
+
++ if (r->Link.CombineConnectPacket)
++ r->Link.ConnectPacket = TRUE;
++
+ if (cp)
+ return RTMP_SendPacket(r, cp, TRUE);
+
+@@ -1667,7 +1724,7 @@ SendUsherToken(RTMP *r, AVal *usherToken)
+ packet.m_hasAbsTimestamp = 0;
+ packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE;
+
+- RTMP_Log(RTMP_LOGDEBUG, "UsherToken: %s", usherToken->av_val);
++ RTMP_Log(RTMP_LOGDEBUG, "UsherToken: %.*s", usherToken->av_len, usherToken->av_val);
+ enc = packet.m_body;
+ enc = AMF_EncodeString(enc, pend, &av_NetStream_Authenticate_UsherToken);
+ enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes);
+@@ -2096,10 +2153,8 @@ SendPlay(RTMP *r)
+ enc = AMF_EncodeNumber(enc, pend, -1000.0);
+ else
+ {
+- if (r->Link.seekTime > 0.0)
+- enc = AMF_EncodeNumber(enc, pend, r->Link.seekTime); /* resume from here */
+- else
+- enc = AMF_EncodeNumber(enc, pend, 0.0); /*-2000.0);*/ /* recorded as default, -2000.0 is not reliable since that freezes the player if the stream is not found */
++ if (r->Link.seekTime > 0.0 || r->Link.stopTime)
++ enc = AMF_EncodeNumber(enc, pend, r->Link.seekTime); /* resume from here */
+ }
+ if (!enc)
+ return FALSE;
+@@ -2215,7 +2270,7 @@ RTMP_SendCtrl(RTMP *r, short nType, unsigned int nObject, unsigned int nTime)
+ int nSize;
+ char *buf;
+
+- RTMP_Log(RTMP_LOGDEBUG, "sending ctrl. type: 0x%04x", (unsigned short)nType);
++ RTMP_Log(RTMP_LOGDEBUG, "sending ctrl, type: 0x%04x", (unsigned short)nType);
+
+ packet.m_nChannel = 0x02; /* control channel (ping) */
+ packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM;
+@@ -2247,8 +2302,8 @@ RTMP_SendCtrl(RTMP *r, short nType, unsigned int nObject, unsigned int nTime)
+ }
+ else if (nType == 0x1A)
+ {
+- *buf = nObject & 0xff;
+- }
++ *buf = nObject & 0xff;
++ }
+ else
+ {
+ if (nSize > 2)
+@@ -2305,6 +2360,7 @@ AV_clear(RTMP_METHOD *vals, int num)
+ free(vals);
+ }
+
++SAVC(onBWCheck);
+ SAVC(onBWDone);
+ SAVC(onFCSubscribe);
+ SAVC(onFCUnsubscribe);
+@@ -2314,24 +2370,26 @@ SAVC(_error);
+ SAVC(close);
+ SAVC(code);
+ SAVC(level);
++SAVC(description);
+ SAVC(onStatus);
+ SAVC(playlist_ready);
+ static const AVal av_NetStream_Failed = AVC("NetStream.Failed");
+ static const AVal av_NetStream_Play_Failed = AVC("NetStream.Play.Failed");
+-static const AVal av_NetStream_Play_StreamNotFound =
+-AVC("NetStream.Play.StreamNotFound");
+-static const AVal av_NetConnection_Connect_InvalidApp =
+-AVC("NetConnection.Connect.InvalidApp");
++static const AVal av_NetStream_Play_StreamNotFound = AVC("NetStream.Play.StreamNotFound");
++static const AVal av_NetConnection_Connect_InvalidApp = AVC("NetConnection.Connect.InvalidApp");
+ static const AVal av_NetStream_Play_Start = AVC("NetStream.Play.Start");
+ static const AVal av_NetStream_Play_Complete = AVC("NetStream.Play.Complete");
+ static const AVal av_NetStream_Play_Stop = AVC("NetStream.Play.Stop");
+ static const AVal av_NetStream_Seek_Notify = AVC("NetStream.Seek.Notify");
+ static const AVal av_NetStream_Pause_Notify = AVC("NetStream.Pause.Notify");
+-static const AVal av_NetStream_Play_PublishNotify =
+-AVC("NetStream.Play.PublishNotify");
+-static const AVal av_NetStream_Play_UnpublishNotify =
+-AVC("NetStream.Play.UnpublishNotify");
++static const AVal av_NetStream_Play_PublishNotify = AVC("NetStream.Play.PublishNotify");
++static const AVal av_NetStream_Play_UnpublishNotify = AVC("NetStream.Play.UnpublishNotify");
+ static const AVal av_NetStream_Publish_Start = AVC("NetStream.Publish.Start");
++static const AVal av_NetConnection_confStream = AVC("NetConnection.confStream");
++static const AVal av_verifyClient = AVC("verifyClient");
++static const AVal av_sendStatus = AVC("sendStatus");
++static const AVal av_getStreamLength = AVC("getStreamLength");
++static const AVal av_ReceiveCheckPublicStatus = AVC("ReceiveCheckPublicStatus");
+
+ /* Returns 0 for OK/Failed/error, 1 for 'Stop or Complete' */
+ static int
+@@ -2341,6 +2399,11 @@ HandleInvoke(RTMP *r, const char *body, unsigned int nBodySize)
+ AVal method;
+ double txn;
+ int ret = 0, nRes;
++ char pbuf[256], *pend = pbuf + sizeof (pbuf), *enc, **params = NULL;
++ char *host = r->Link.hostname.av_len ? r->Link.hostname.av_val : "";
++ char *pageUrl = r->Link.pageUrl.av_len ? r->Link.pageUrl.av_val : "";
++ int param_count;
++ AVal av_Command, av_Response;
+ if (body[0] != 0x02) /* make sure it is a string method name we start with */
+ {
+ RTMP_Log(RTMP_LOGWARNING, "%s, Sanity failed. no string method in invoke packet",
+@@ -2402,23 +2465,137 @@ HandleInvoke(RTMP *r, const char *body, unsigned int nBodySize)
+ RTMP_SendServerBW(r);
+ RTMP_SendCtrl(r, 3, 0, 300);
+ }
+- RTMP_SendCreateStream(r);
+-
+- if (!(r->Link.protocol & RTMP_FEATURE_WRITE))
+- {
+- /* Authenticate on Justin.tv legacy servers before sending FCSubscribe */
+- if (r->Link.usherToken.av_len)
+- SendUsherToken(r, &r->Link.usherToken);
+- /* Send the FCSubscribe if live stream or if subscribepath is set */
+- if (r->Link.subscribepath.av_len)
+- SendFCSubscribe(r, &r->Link.subscribepath);
+- else if (r->Link.lFlags & RTMP_LF_LIVE)
+- SendFCSubscribe(r, &r->Link.playpath);
+- }
+- }
++ if (strstr(host, "tv-stream.to") || strstr(pageUrl, "tv-stream.to"))
++ {
++ static char auth[] = {'h', 0xC2, 0xA7, '4', 'j', 'h', 'H', '4', '3', 'd'};
++ AVal av_auth;
++ SAVC(requestAccess);
++ av_auth.av_val = auth;
++ av_auth.av_len = sizeof (auth);
++
++ enc = pbuf;
++ enc = AMF_EncodeString(enc, pend, &av_requestAccess);
++ enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes);
++ *enc++ = AMF_NULL;
++ enc = AMF_EncodeString(enc, pend, &av_auth);
++ av_Command.av_val = pbuf;
++ av_Command.av_len = enc - pbuf;
++ SendInvoke(r, &av_Command, FALSE);
++
++ SAVC(getConnectionCount);
++ enc = pbuf;
++ enc = AMF_EncodeString(enc, pend, &av_getConnectionCount);
++ enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes);
++ *enc++ = AMF_NULL;
++ av_Command.av_val = pbuf;
++ av_Command.av_len = enc - pbuf;
++ SendInvoke(r, &av_Command, FALSE);
++
++ SendGetStreamLength(r);
++ }
++ else if (strstr(host, "jampo.com.ua") || strstr(pageUrl, "jampo.com.ua"))
++ {
++ SendGetStreamLength(r);
++ }
++ else if (strstr(host, "streamscene.cc") || strstr(pageUrl, "streamscene.cc")
++ || strstr(host, "tsboard.tv") || strstr(pageUrl, "teamstream.in"))
++ {
++ SAVC(r);
++ enc = pbuf;
++ enc = AMF_EncodeString(enc, pend, &av_r);
++ enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes);
++ *enc++ = AMF_NULL;
++ av_Command.av_val = pbuf;
++ av_Command.av_len = enc - pbuf;
++ SendInvoke(r, &av_Command, FALSE);
++
++ SendGetStreamLength(r);
++ }
++ else if (strstr(host, "chaturbate.com") || strstr(pageUrl, "chaturbate.com"))
++ {
++ AVal av_ModelName;
++ SAVC(CheckPublicStatus);
++
++ if (strlen(pageUrl) > 7)
++ {
++ strsplit(pageUrl + 7, FALSE, '/', ¶ms);
++ av_ModelName.av_val = params[1];
++ av_ModelName.av_len = strlen(params[1]);
++
++ enc = pbuf;
++ enc = AMF_EncodeString(enc, pend, &av_CheckPublicStatus);
++ enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes);
++ *enc++ = AMF_NULL;
++ enc = AMF_EncodeString(enc, pend, &av_ModelName);
++ av_Command.av_val = pbuf;
++ av_Command.av_len = enc - pbuf;
++
++ SendInvoke(r, &av_Command, FALSE);
++ }
++ else
++ {
++ RTMP_Log(RTMP_LOGERROR, "you must specify the pageUrl");
++ RTMP_Close(r);
++ }
++ }
++ /* Weeb.tv specific authentication */
++ else if (r->Link.WeebToken.av_len)
++ {
++ AVal av_Token, av_Username, av_Password;
++ SAVC(determineAccess);
++
++ param_count = strsplit(r->Link.WeebToken.av_val, FALSE, ';', ¶ms);
++ if (param_count >= 1)
++ {
++ av_Token.av_val = params[0];
++ av_Token.av_len = strlen(params[0]);
++ }
++ if (param_count >= 2)
++ {
++ av_Username.av_val = params[1];
++ av_Username.av_len = strlen(params[1]);
++ }
++ if (param_count >= 3)
++ {
++ av_Password.av_val = params[2];
++ av_Password.av_len = strlen(params[2]);
++ }
++
++ enc = pbuf;
++ enc = AMF_EncodeString(enc, pend, &av_determineAccess);
++ enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes);
++ *enc++ = AMF_NULL;
++ enc = AMF_EncodeString(enc, pend, &av_Token);
++ enc = AMF_EncodeString(enc, pend, &av_Username);
++ enc = AMF_EncodeString(enc, pend, &av_Password);
++ av_Command.av_val = pbuf;
++ av_Command.av_len = enc - pbuf;
++
++ RTMP_Log(RTMP_LOGDEBUG, "WeebToken: %s", r->Link.WeebToken.av_val);
++ SendInvoke(r, &av_Command, FALSE);
++ }
++ else
++ RTMP_SendCreateStream(r);
++ }
++ else if (AVMATCH(&methodInvoked, &av_getStreamLength))
++ {
++ RTMP_SendCreateStream(r);
++ }
+ else if (AVMATCH(&methodInvoked, &av_createStream))
+- {
+- r->m_stream_id = (int)AMFProp_GetNumber(AMF_GetProp(&obj, NULL, 3));
++ {
++ r->m_stream_id = (int) AMFProp_GetNumber(AMF_GetProp(&obj, NULL, 3));
++
++ if (!(r->Link.protocol & RTMP_FEATURE_WRITE))
++ {
++ /* Authenticate on Justin.tv legacy servers before sending FCSubscribe */
++ if (r->Link.usherToken.av_len)
++ SendUsherToken(r, &r->Link.usherToken);
++ /* Send the FCSubscribe if live stream or if subscribepath is set */
++ if (r->Link.subscribepath.av_len)
++ SendFCSubscribe(r, &r->Link.subscribepath);
++ else if ((r->Link.lFlags & RTMP_LF_LIVE) && (!r->Link.WeebToken.av_len))
++ SendFCSubscribe(r, &r->Link.playpath);
++ }
+
+ if (r->Link.protocol & RTMP_FEATURE_WRITE)
+ {
+@@ -2441,7 +2618,7 @@ HandleInvoke(RTMP *r, const char *body, unsigned int nBodySize)
+ }
+ else if (AVMATCH(&method, &av_onBWDone))
+ {
+- if (!r->m_nBWCheckCounter)
++ if (!r->m_nBWCheckCounter)
+ SendCheckBW(r);
+ }
+ else if (AVMATCH(&method, &av_onFCSubscribe))
+@@ -2457,7 +2634,7 @@ HandleInvoke(RTMP *r, const char *body, unsigned int nBodySize)
+ {
+ SendPong(r, txn);
+ }
+- else if (AVMATCH(&method, &av__onbwcheck))
++ else if (AVMATCH(&method, &av__onbwcheck) || AVMATCH(&method, &av_onBWCheck))
+ {
+ SendCheckBWResult(r, txn);
+ }
+@@ -2473,20 +2650,63 @@ HandleInvoke(RTMP *r, const char *body, unsigned int nBodySize)
+ }
+ else if (AVMATCH(&method, &av__error))
+ {
+- RTMP_Log(RTMP_LOGERROR, "rtmp server sent error");
++ double code = 0;
++ unsigned int parsedPort;
++ AMFObject obj2;
++ AMFObjectProperty p;
++ AVal redirect;
++ SAVC(ex);
++ SAVC(redirect);
++
++ AMFProp_GetObject(AMF_GetProp(&obj, NULL, 3), &obj2);
++ if (RTMP_FindFirstMatchingProperty(&obj2, &av_ex, &p))
++ {
++ AMFProp_GetObject(&p, &obj2);
++ if (RTMP_FindFirstMatchingProperty(&obj2, &av_code, &p))
++ code = AMFProp_GetNumber(&p);
++ if (code == 302 && RTMP_FindFirstMatchingProperty(&obj2, &av_redirect, &p))
++ {
++ AMFProp_GetString(&p, &redirect);
++ r->Link.redirected = TRUE;
++
++ char *url = malloc(redirect.av_len + sizeof ("/playpath"));
++ strncpy(url, redirect.av_val, redirect.av_len);
++ url[redirect.av_len] = '\0';
++ r->Link.tcUrl.av_val = url;
++ r->Link.tcUrl.av_len = redirect.av_len;
++ strcat(url, "/playpath");
++ RTMP_ParseURL(url, &r->Link.protocol, &r->Link.hostname, &parsedPort, &r->Link.playpath0, &r->Link.app);
++ r->Link.port = parsedPort;
++ }
++ }
++ if (r->Link.redirected)
++ RTMP_Log(RTMP_LOGINFO, "rtmp server sent redirect");
++ else
++ RTMP_Log(RTMP_LOGERROR, "rtmp server sent error");
+ }
+ else if (AVMATCH(&method, &av_close))
+ {
+- RTMP_Log(RTMP_LOGERROR, "rtmp server requested close");
+- RTMP_Close(r);
++ if (r->Link.redirected)
++ {
++ RTMP_Log(RTMP_LOGINFO, "trying to connect with redirected url");
++ RTMP_Close(r);
++ r->Link.redirected = FALSE;
++ RTMP_Connect(r, NULL);
++ }
++ else
++ {
++ RTMP_Log(RTMP_LOGERROR, "rtmp server requested close");
++ RTMP_Close(r);
++ }
+ }
+ else if (AVMATCH(&method, &av_onStatus))
+ {
+ AMFObject obj2;
+- AVal code, level;
++ AVal code, level, description;
+ AMFProp_GetObject(AMF_GetProp(&obj, NULL, 3), &obj2);
+ AMFProp_GetString(AMF_GetProp(&obj2, &av_code, -1), &code);
+ AMFProp_GetString(AMF_GetProp(&obj2, &av_level, -1), &level);
++ AMFProp_GetString(AMF_GetProp(&obj2, &av_description, -1), &description);
+
+ RTMP_Log(RTMP_LOGDEBUG, "%s, onStatus: %s", __FUNCTION__, code.av_val);
+ if (AVMATCH(&code, &av_NetStream_Failed)
+@@ -2550,6 +2770,45 @@ HandleInvoke(RTMP *r, const char *body, unsigned int nBodySize)
+ r->m_pausing = 3;
+ }
+ }
++
++ else if (AVMATCH(&code, &av_NetConnection_confStream))
++ {
++#ifdef CRYPTO
++ static const char hexdig[] = "0123456789abcdef";
++ SAVC(cf_stream);
++ int i;
++ char hash_hex[33] = {0};
++ unsigned char hash[16];
++ AVal auth;
++ param_count = strsplit(description.av_val, description.av_len, ':', ¶ms);
++ if (param_count >= 3)
++ {
++ char *buf = malloc(strlen(params[0]) + r->Link.playpath.av_len + 1);
++ strcpy(buf, params[0]);
++ strncat(buf, r->Link.playpath.av_val, r->Link.playpath.av_len);
++ md5_hash((unsigned char *) buf, strlen(buf), hash);
++ for (i = 0; i < 16; i++)
++ {
++ hash_hex[i * 2] = hexdig[0x0f & (hash[i] >> 4)];
++ hash_hex[i * 2 + 1] = hexdig[0x0f & (hash[i])];
++ }
++ auth.av_val = &hash_hex[atoi(params[1]) - 1];
++ auth.av_len = atoi(params[2]);
++ RTMP_Log(RTMP_LOGDEBUG, "Khalsa: %.*s", auth.av_len, auth.av_val);
++
++ enc = pbuf;
++ enc = AMF_EncodeString(enc, pend, &av_cf_stream);
++ enc = AMF_EncodeNumber(enc, pend, txn);
++ *enc++ = AMF_NULL;
++ enc = AMF_EncodeString(enc, pend, &auth);
++ av_Command.av_val = pbuf;
++ av_Command.av_len = enc - pbuf;
++
++ SendInvoke(r, &av_Command, FALSE);
++ free(buf);
++ }
++#endif
++ }
+ }
+ else if (AVMATCH(&method, &av_playlist_ready))
+ {
+@@ -2563,6 +2822,74 @@ HandleInvoke(RTMP *r, const char *body, unsigned int nBodySize)
+ }
+ }
+ }
++ else if (AVMATCH(&method, &av_verifyClient))
++ {
++ double VerificationNumber = AMFProp_GetNumber(AMF_GetProp(&obj, NULL, 3));
++ RTMP_Log(RTMP_LOGDEBUG, "VerificationNumber: %.2f", VerificationNumber);
++
++ enc = pbuf;
++ enc = AMF_EncodeString(enc, pend, &av__result);
++ enc = AMF_EncodeNumber(enc, pend, txn);
++ *enc++ = AMF_NULL;
++ enc = AMF_EncodeNumber(enc, pend, exp(atan(sqrt(VerificationNumber))) + 1);
++ av_Response.av_val = pbuf;
++ av_Response.av_len = enc - pbuf;
++
++ AMF_Decode(&obj, av_Response.av_val, av_Response.av_len, FALSE);
++ AMF_Dump(&obj);
++ SendInvoke(r, &av_Response, FALSE);
++ }
++ else if (AVMATCH(&method, &av_sendStatus))
++ {
++ if (r->Link.WeebToken.av_len)
++ {
++ AVal av_Authorized = AVC("User.hasAccess");
++ AVal av_TransferLimit = AVC("User.noPremium.limited");
++ AVal av_UserLimit = AVC("User.noPremium.tooManyUsers");
++ AVal av_TimeLeft = AVC("timeLeft");
++ AVal av_Status, av_ReconnectionTime;
++
++ AMFObject Status;
++ AMFProp_GetObject(AMF_GetProp(&obj, NULL, 3), &Status);
++ AMFProp_GetString(AMF_GetProp(&Status, &av_code, -1), &av_Status);
++ RTMP_Log(RTMP_LOGINFO, "%.*s", av_Status.av_len, av_Status.av_val);
++ if (AVMATCH(&av_Status, &av_Authorized))
++ {
++ RTMP_Log(RTMP_LOGINFO, "Weeb.tv authentication successful");
++ RTMP_SendCreateStream(r);
++ }
++ else if (AVMATCH(&av_Status, &av_UserLimit))
++ {
++ RTMP_Log(RTMP_LOGINFO, "No free slots available");
++ RTMP_Close(r);
++ }
++ else if (AVMATCH(&av_Status, &av_TransferLimit))
++ {
++ AMFProp_GetString(AMF_GetProp(&Status, &av_TimeLeft, -1), &av_ReconnectionTime);
++ RTMP_Log(RTMP_LOGINFO, "Viewing limit exceeded. try again in %.*s minutes.", av_ReconnectionTime.av_len, av_ReconnectionTime.av_val);
++ RTMP_Close(r);
++ }
++ }
++ }
++ else if (AVMATCH(&method, &av_ReceiveCheckPublicStatus))
++ {
++ AVal Status;
++ AMFProp_GetString(AMF_GetProp(&obj, NULL, 3), &Status);
++ strsplit(Status.av_val, Status.av_len, ',', ¶ms);
++ if (strcmp(params[0], "0") == 0)
++ {
++ RTMP_Log(RTMP_LOGINFO, "Model status is %s", params[1]);
++ RTMP_Close(r);
++ }
++ else
++ {
++ AVal Playpath;
++ Playpath.av_val = params[1];
++ Playpath.av_len = strlen(params[1]);
++ RTMP_ParsePlaypath(&Playpath, &r->Link.playpath);
++ RTMP_SendCreateStream(r);
++ }
++ }
+ else
+ {
+
+@@ -2748,7 +3075,7 @@ HandleCtrl(RTMP *r, const RTMPPacket *packet)
+ unsigned int tmp;
+ if (packet->m_body && packet->m_nBodySize >= 2)
+ nType = AMF_DecodeInt16(packet->m_body);
+- RTMP_Log(RTMP_LOGDEBUG, "%s, received ctrl. type: %d, len: %d", __FUNCTION__, nType,
++ RTMP_Log(RTMP_LOGDEBUG, "%s, received ctrl, type: %d, len: %d", __FUNCTION__, nType,
+ packet->m_nBodySize);
+ /*RTMP_LogHex(packet.m_body, packet.m_nBodySize); */
+
+@@ -2856,15 +3183,15 @@ HandleCtrl(RTMP *r, const RTMPPacket *packet)
+ RTMP_Log(RTMP_LOGDEBUG, "%s, SWFVerification ping received: ", __FUNCTION__);
+ if (packet->m_nBodySize > 2 && packet->m_body[2] > 0x01)
+ {
+- RTMP_Log(RTMP_LOGERROR,
+- "%s: SWFVerification Type %d request not supported! Patches welcome...",
+- __FUNCTION__, packet->m_body[2]);
++ RTMP_Log(RTMP_LOGERROR,
++ "%s: SWFVerification Type %d request not supported, attempting to use SWFVerification Type 1! Patches welcome...",
++ __FUNCTION__, packet->m_body[2]);
+ }
+ #ifdef CRYPTO
+ /*RTMP_LogHex(packet.m_body, packet.m_nBodySize); */
+
+ /* respond with HMAC SHA256 of decompressed SWF, key is the 30byte player key, also the last 30 bytes of the server handshake are applied */
+- else if (r->Link.SWFSize)
++ if (r->Link.SWFSize)
+ {
+ RTMP_SendCtrl(r, 0x1B, 0, 0);
+ }
+@@ -3142,8 +3469,18 @@ HandShake(RTMP *r, int FP9HandShake)
+ serversig[4], serversig[5], serversig[6], serversig[7]);
+
+ /* 2nd part of handshake */
+- if (!WriteN(r, serversig, RTMP_SIG_SIZE))
+- return FALSE;
++ if (r->Link.CombineConnectPacket)
++ {
++ char *HandshakeResponse = malloc(RTMP_SIG_SIZE);
++ memcpy(HandshakeResponse, (char *) serversig, RTMP_SIG_SIZE);
++ r->Link.HandshakeResponse.av_val = HandshakeResponse;
++ r->Link.HandshakeResponse.av_len = RTMP_SIG_SIZE;
++ }
++ else
++ {
++ if (!WriteN(r, (char *) serversig, RTMP_SIG_SIZE))
++ return FALSE;
++ }
+
+ if (ReadN(r, serversig, RTMP_SIG_SIZE) != RTMP_SIG_SIZE)
+ return FALSE;
+@@ -3709,12 +4046,11 @@ HTTP_Post(RTMP *r, RTMPTCmd cmd, const char *buf, int len)
+ char hbuf[512];
+ int hlen = snprintf(hbuf, sizeof(hbuf), "POST /%s%s/%d HTTP/1.1\r\n"
+ "Host: %.*s:%d\r\n"
+- "Accept: */*\r\n"
+- "User-Agent: Shockwave Flash\n"
+- "Connection: Keep-Alive\n"
++ "User-Agent: Shockwave Flash\r\n"
++ "Connection: Keep-Alive\r\n"
+ "Cache-Control: no-cache\r\n"
+- "Content-type: application/x-fcs\r\n"
+- "Content-length: %d\r\n\r\n", RTMPT_cmds[cmd],
++ "Content-Type: application/x-fcs\r\n"
++ "Content-Length: %d\r\n\r\n", RTMPT_cmds[cmd],
+ r->m_clientID.av_val ? r->m_clientID.av_val : "",
+ r->m_msgCounter, r->Link.hostname.av_len, r->Link.hostname.av_val,
+ r->Link.port, len);
+@@ -3749,6 +4085,14 @@ HTTP_read(RTMP *r, int fill)
+ if (!ptr)
+ return -1;
+ ptr += 4;
++ int resplen = r->m_sb.sb_size - (ptr - r->m_sb.sb_start);
++ if (hlen < 4096)
++ while (resplen < hlen)
++ {
++ if (RTMPSockBuf_Fill(&r->m_sb) == -1)
++ return -1;
++ resplen = r->m_sb.sb_size - (ptr - r->m_sb.sb_start);
++ }
+ r->m_sb.sb_size -= ptr - r->m_sb.sb_start;
+ r->m_sb.sb_start = ptr;
+ r->m_unackd--;
+@@ -4301,13 +4645,21 @@ fail:
+ r->m_read.status = nRead;
+ goto fail;
+ }
+- /* buffer overflow, fix buffer and give up */
+- if (r->m_read.buf < mybuf || r->m_read.buf > end) {
+- mybuf = realloc(mybuf, cnt + nRead);
+- memcpy(mybuf+cnt, r->m_read.buf, nRead);
+- r->m_read.buf = mybuf+cnt+nRead;
+- break;
+- }
++ /* buffer overflow, fix buffer and give up */
++ if (r->m_read.buf < mybuf || r->m_read.buf > end)
++ {
++ if (!cnt)
++ {
++ mybuf = realloc(mybuf, sizeof (flvHeader) + cnt + nRead);
++ memcpy(mybuf, flvHeader, sizeof (flvHeader));
++ cnt += sizeof (flvHeader);
++ }
++ else
++ mybuf = realloc(mybuf, cnt + nRead);
++ memcpy(mybuf + cnt, r->m_read.buf, nRead);
++ r->m_read.buf = mybuf + cnt + nRead;
++ break;
++ }
+ cnt += nRead;
+ r->m_read.buf += nRead;
+ r->m_read.buflen -= nRead;
+@@ -4458,3 +4810,90 @@ RTMP_Write(RTMP *r, const char *buf, int size)
+ }
+ return size+s2;
+ }
++
++static int
++SendInvoke(RTMP *r, AVal *Command, int queue)
++{
++ RTMPPacket packet;
++ char pbuf[512], *enc;
++
++ packet.m_nChannel = 0x03; /* control channel (invoke) */
++ packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM;
++ packet.m_packetType = RTMP_PACKET_TYPE_INVOKE;
++ packet.m_nTimeStamp = 0;
++ packet.m_nInfoField2 = 0;
++ packet.m_hasAbsTimestamp = 0;
++ packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE;
++
++ enc = packet.m_body;
++ if (Command->av_len)
++ {
++ memcpy(enc, Command->av_val, Command->av_len);
++ enc += Command->av_len;
++ }
++ else
++ return FALSE;
++ packet.m_nBodySize = enc - packet.m_body;
++
++ return RTMP_SendPacket(r, &packet, queue);
++}
++
++static int
++strsplit(char *src, int srclen, char delim, char ***params)
++{
++ char *sptr, *srcbeg, *srcend, *dstr;
++ int count = 1, i = 0, len = 0;
++
++ if (src == NULL)
++ return 0;
++ if (!srclen)
++ srclen = strlen(src);
++ srcbeg = src;
++ srcend = srcbeg + srclen;
++ sptr = srcbeg;
++
++ /* count the delimiters */
++ while (sptr < srcend)
++ {
++ if (*sptr++ == delim)
++ count++;
++ }
++ sptr = srcbeg;
++ *params = calloc(count, sizeof (size_t));
++ char **param = *params;
++
++ for (i = 0; i < (count - 1); i++)
++ {
++ dstr = strchr(sptr, delim);
++ len = dstr - sptr;
++ param[i] = calloc(len + 1, sizeof (char));
++ strncpy(param[i], sptr, len);
++ sptr += len + 1;
++ }
++
++ /* copy the last string */
++ if (sptr <= srcend)
++ {
++ len = srclen - (sptr - srcbeg);
++ param[i] = calloc(len + 1, sizeof (char));
++ strncpy(param[i], sptr, len);
++ }
++ return count;
++}
++
++static int
++SendGetStreamLength(RTMP *r)
++{
++ char pbuf[256], *pend = pbuf + sizeof (pbuf), *enc;
++ AVal av_Command;
++
++ enc = pbuf;
++ enc = AMF_EncodeString(enc, pend, &av_getStreamLength);
++ enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes);
++ *enc++ = AMF_NULL;
++ enc = AMF_EncodeString(enc, pend, &r->Link.playpath);
++ av_Command.av_val = pbuf;
++ av_Command.av_len = enc - pbuf;
++
++ return SendInvoke(r, &av_Command, TRUE);
++}
+diff --git a/librtmp/rtmp.h b/librtmp/rtmp.h
+index 6b2ae5b..411b488 100644
+--- a/librtmp/rtmp.h
++++ b/librtmp/rtmp.h
+@@ -150,12 +150,14 @@ extern "C"
+ AVal playpath; /* passed in explicitly */
+ AVal tcUrl;
+ AVal swfUrl;
++ AVal swfHash;
+ AVal pageUrl;
+ AVal app;
+ AVal auth;
+ AVal flashVer;
+ AVal subscribepath;
+ AVal usherToken;
++ AVal WeebToken;
+ AVal token;
+ AMFObject extras;
+ int edepth;
+@@ -172,9 +174,15 @@ extern "C"
+ int lFlags;
+
+ int swfAge;
++ int swfSize;
+
+ int protocol;
++ int ConnectPacket;
++ int CombineConnectPacket;
++ int redirected;
+ int timeout; /* connection timeout in seconds */
++ AVal Extras;
++ AVal HandshakeResponse;
+
+ unsigned short socksport;
+ unsigned short port;
+@@ -299,6 +307,7 @@ extern "C"
+ AVal *flashVer,
+ AVal *subscribepath,
+ AVal *usherToken,
++ AVal *WeebToken,
+ int dStart,
+ int dStop, int bLiveStream, long int timeout);
+
+diff --git a/librtmp/rtmp_sys.h b/librtmp/rtmp_sys.h
+index c3fd4a6..1bfb562 100644
+--- a/librtmp/rtmp_sys.h
++++ b/librtmp/rtmp_sys.h
+@@ -64,6 +64,7 @@
+ #include <polarssl/net.h>
+ #include <polarssl/ssl.h>
+ #include <polarssl/havege.h>
++#include <polarssl/md5.h>
+ typedef struct tls_ctx {
+ havege_state hs;
+ ssl_session ssn;
+@@ -71,7 +72,7 @@ typedef struct tls_ctx {
+ #define TLS_CTX tls_ctx *
+ #define TLS_client(ctx,s) s = malloc(sizeof(ssl_context)); ssl_init(s);\
+ ssl_set_endpoint(s, SSL_IS_CLIENT); ssl_set_authmode(s, SSL_VERIFY_NONE);\
+- ssl_set_rng(s, havege_rand, &ctx->hs);\
++ ssl_set_rng(s, havege_random, &ctx->hs);\
+ ssl_set_ciphersuites(s, ssl_default_ciphersuites);\
+ ssl_set_session(s, 1, 600, &ctx->ssn)
+ #define TLS_setfd(s,fd) ssl_set_bio(s, net_recv, &fd, net_send, &fd)
+@@ -80,6 +81,7 @@ typedef struct tls_ctx {
+ #define TLS_write(s,b,l) ssl_write(s,(unsigned char *)b,l)
+ #define TLS_shutdown(s) ssl_close_notify(s)
+ #define TLS_close(s) ssl_free(s); free(s)
++#define md5_hash(i, ilen, o) md5(i, ilen, o)
+
+ #elif defined(USE_GNUTLS)
+ #include <gnutls/gnutls.h>
+@@ -95,6 +97,8 @@ typedef struct tls_ctx {
+ #define TLS_write(s,b,l) gnutls_record_send(s,b,l)
+ #define TLS_shutdown(s) gnutls_bye(s, GNUTLS_SHUT_RDWR)
+ #define TLS_close(s) gnutls_deinit(s)
++#define md5_hash(i, ilen, o) gnutls_digest_algorithm_t algorithm = GNUTLS_DIG_MD5;\
++ gnutls_hash_fast(algorithm, i, ilen, o);
+
+ #else /* USE_OPENSSL */
+ #define TLS_CTX SSL_CTX *
+@@ -105,6 +109,7 @@ typedef struct tls_ctx {
+ #define TLS_write(s,b,l) SSL_write(s,b,l)
+ #define TLS_shutdown(s) SSL_shutdown(s)
+ #define TLS_close(s) SSL_free(s)
++#define md5_hash(i, ilen, o) MD5(i, ilen, o)
+
+ #endif
+ #endif
+diff --git a/rtmpdump.c b/rtmpdump.c
+index e52f7d4..7bb0890 100644
+--- a/rtmpdump.c
++++ b/rtmpdump.c
+@@ -701,6 +701,8 @@ void usage(char *prog)
+ RTMP_LogPrintf
+ ("--jtv|-j JSON Authentication token for Justin.tv legacy servers\n");
+ RTMP_LogPrintf
++ ("--weeb|-J string Authentication token for weeb.tv servers\n");
++ RTMP_LogPrintf
+ ("--hashes|-# Display progress with hashes, not with the byte counter\n");
+ RTMP_LogPrintf
+ ("--buffer|-b Buffer time in milliseconds (default: %u)\n",
+@@ -747,7 +749,8 @@ main(int argc, char **argv)
+ AVal hostname = { 0, 0 };
+ AVal playpath = { 0, 0 };
+ AVal subscribepath = { 0, 0 };
+- AVal usherToken = { 0, 0 }; //Justin.tv auth token
++ AVal usherToken = { 0, 0 }; // Justin.tv auth token
++ AVal WeebToken = { 0, 0 }; // Weeb.tv auth token
+ int port = -1;
+ int protocol = RTMP_PROTOCOL_UNDEFINED;
+ int retries = 0;
+@@ -852,12 +855,13 @@ main(int argc, char **argv)
+ {"quiet", 0, NULL, 'q'},
+ {"verbose", 0, NULL, 'V'},
+ {"jtv", 1, NULL, 'j'},
++ {"weeb", 1, NULL, 'J'},
+ {0, 0, 0, 0}
+ };
+
+ while ((opt =
+ getopt_long(argc, argv,
+- "hVveqzRr:s:t:p:a:b:f:o:u:C:n:c:l:y:Ym:k:d:A:B:T:w:x:W:X:S:#j:",
++ "hVveqzr:s:t:p:a:b:f:o:u:C:n:c:l:y:Ym:k:d:A:B:T:w:x:W:X:S:#j:J:",
+ longopts, NULL)) != -1)
+ {
+ switch (opt)
+@@ -1070,6 +1074,9 @@ main(int argc, char **argv)
+ case 'j':
+ STR2AVAL(usherToken, optarg);
+ break;
++ case 'J':
++ STR2AVAL(WeebToken, optarg);
++ break;
+ default:
+ RTMP_LogPrintf("unknown option: %c\n", opt);
+ usage(argv[0]);
+@@ -1161,14 +1168,14 @@ main(int argc, char **argv)
+
+ if (tcUrl.av_len == 0)
+ {
+- tcUrl.av_len = strlen(RTMPProtocolStringsLower[protocol]) +
+- hostname.av_len + app.av_len + sizeof("://:65535/");
++ tcUrl.av_len = strlen(RTMPProtocolStringsLower[protocol]) +
++ hostname.av_len + app.av_len + sizeof ("://:65535/");
+ tcUrl.av_val = (char *) malloc(tcUrl.av_len);
+- if (!tcUrl.av_val)
+- return RD_FAILED;
++ if (!tcUrl.av_val)
++ return RD_FAILED;
+ tcUrl.av_len = snprintf(tcUrl.av_val, tcUrl.av_len, "%s://%.*s:%d/%.*s",
+- RTMPProtocolStringsLower[protocol], hostname.av_len,
+- hostname.av_val, port, app.av_len, app.av_val);
++ RTMPProtocolStringsLower[protocol], hostname.av_len,
++ hostname.av_val, port, app.av_len, app.av_val);
+ }
+
+ int first = 1;
+@@ -1187,7 +1194,7 @@ main(int argc, char **argv)
+
+ RTMP_SetupStream(&rtmp, protocol, &hostname, port, &sockshost, &playpath,
+ &tcUrl, &swfUrl, &pageUrl, &app, &auth, &swfHash, swfSize,
+- &flashVer, &subscribepath, &usherToken, dSeek, dStopOffset, bLiveStream, timeout);
++ &flashVer, &subscribepath, &usherToken, &WeebToken, dSeek, dStopOffset, bLiveStream, timeout);
+
+ /* Try to keep the stream moving if it pauses on us */
+ if (!bLiveStream && !bRealtimeStream && !(protocol & RTMP_FEATURE_HTTP))
+diff --git a/rtmpgw.c b/rtmpgw.c
+index 0cf56bb..cd4396d 100644
+--- a/rtmpgw.c
++++ b/rtmpgw.c
+@@ -95,7 +95,8 @@ typedef struct
+ AVal flashVer;
+ AVal token;
+ AVal subscribepath;
+- AVal usherToken; //Justin.tv auth token
++ AVal usherToken; // Justin.tv auth token
++ AVal WeebToken; // Weeb.tv auth token
+ AVal sockshost;
+ AMFObject extras;
+ int edepth;
+@@ -553,7 +554,7 @@ void processTCPrequest(STREAMING_SERVER * server, // server socket and state (ou
+ RTMP_Init(&rtmp);
+ RTMP_SetBufferMS(&rtmp, req.bufferTime);
+ RTMP_SetupStream(&rtmp, req.protocol, &req.hostname, req.rtmpport, &req.sockshost,
+- &req.playpath, &req.tcUrl, &req.swfUrl, &req.pageUrl, &req.app, &req.auth, &req.swfHash, req.swfSize, &req.flashVer, &req.subscribepath, &req.usherToken, dSeek, req.dStopOffset,
++ &req.playpath, &req.tcUrl, &req.swfUrl, &req.pageUrl, &req.app, &req.auth, &req.swfHash, req.swfSize, &req.flashVer, &req.subscribepath, &req.usherToken, &req.WeebToken, dSeek, req.dStopOffset,
+ req.bLiveStream, req.timeout);
+ /* backward compatibility, we always sent this as true before */
+ if (req.auth.av_len)
+@@ -957,6 +958,9 @@ ParseOption(char opt, char *arg, RTMP_REQUEST * req)
+ case 'j':
+ STR2AVAL(req->usherToken, arg);
+ break;
++ case 'J':
++ STR2AVAL(req->WeebToken, arg);
++ break;
+ default:
+ RTMP_LogPrintf("unknown option: %c, arg: %s\n", opt, arg);
+ return FALSE;
+@@ -1028,6 +1032,7 @@ main(int argc, char **argv)
+ {"quiet", 0, NULL, 'q'},
+ {"verbose", 0, NULL, 'V'},
+ {"jtv", 1, NULL, 'j'},
++ {"weeb", 1, NULL, 'J'},
+ {0, 0, 0, 0}
+ };
+
+@@ -1040,7 +1045,7 @@ main(int argc, char **argv)
+
+ while ((opt =
+ getopt_long(argc, argv,
+- "hvqVzr:s:t:p:a:f:u:n:c:l:y:m:d:D:A:B:T:g:w:x:W:X:S:j:", longopts,
++ "hvqVzr:s:t:p:a:f:u:n:c:l:y:m:d:D:A:B:T:g:w:x:W:X:S:j:J:", longopts,
+ NULL)) != -1)
+ {
+ switch (opt)
+@@ -1103,6 +1108,8 @@ main(int argc, char **argv)
+ RTMP_LogPrintf
+ ("--jtv|-j JSON Authentication token for Justin.tv legacy servers\n");
+ RTMP_LogPrintf
++ ("--weeb|-J string Authentication token for weeb.tv servers\n");
++ RTMP_LogPrintf
+ ("--buffer|-b Buffer time in milliseconds (default: %u)\n\n",
+ defaultRTMPRequest.bufferTime);
+
+diff --git a/rtmpsrv.c b/rtmpsrv.c
+index 9aa62f3..9ec8f23 100644
+--- a/rtmpsrv.c
++++ b/rtmpsrv.c
+@@ -96,9 +96,20 @@ STREAMING_SERVER *rtmpServer = 0; // server structure pointer
+ STREAMING_SERVER *startStreaming(const char *address, int port);
+ void stopStreaming(STREAMING_SERVER * server);
+ void AVreplace(AVal *src, const AVal *orig, const AVal *repl);
++char *strreplace(char *srcstr, int srclen, char *orig, char *repl);
++int file_exists(const char *fname);
++int SendCheckBWResponse(RTMP *r, int oldMethodType, int onBWDoneInit);
++AVal AVcopy(AVal src);
++AVal StripParams(AVal *src);
+
+ static const AVal av_dquote = AVC("\"");
+ static const AVal av_escdquote = AVC("\\\"");
++#ifdef WIN32
++static const AVal av_caret = AVC("^");
++static const AVal av_esccaret = AVC("^^");
++static const AVal av_pipe = AVC("|");
++static const AVal av_escpipe = AVC("^|");
++#endif
+
+ typedef struct
+ {
+@@ -167,6 +178,10 @@ SAVC(level);
+ SAVC(code);
+ SAVC(description);
+ SAVC(secureToken);
++SAVC(_checkbw);
++SAVC(_onbwdone);
++SAVC(checkBandwidth);
++SAVC(onBWDone);
+
+ static int
+ SendConnectResult(RTMP *r, double txn)
+@@ -190,7 +205,7 @@ SendConnectResult(RTMP *r, double txn)
+ enc = AMF_EncodeNumber(enc, pend, txn);
+ *enc++ = AMF_OBJECT;
+
+- STR2AVAL(av, "FMS/3,5,1,525");
++ STR2AVAL(av, "FMS/3,5,7,7009");
+ enc = AMF_EncodeNamedString(enc, pend, &av_fmsVer, &av);
+ enc = AMF_EncodeNamedNumber(enc, pend, &av_capabilities, 31.0);
+ enc = AMF_EncodeNamedNumber(enc, pend, &av_mode, 1.0);
+@@ -212,7 +227,7 @@ SendConnectResult(RTMP *r, double txn)
+ enc = AMF_EncodeNamedString(enc, pend, &av_secureToken, &av);
+ #endif
+ STR2AVAL(p.p_name, "version");
+- STR2AVAL(p.p_vu.p_aval, "3,5,1,525");
++ STR2AVAL(p.p_vu.p_aval, "3,5,7,7009");
+ p.p_type = AMF_STRING;
+ obj.o_num = 1;
+ obj.o_props = &p;
+@@ -268,7 +283,7 @@ static int
+ SendPlayStart(RTMP *r)
+ {
+ RTMPPacket packet;
+- char pbuf[512], *pend = pbuf+sizeof(pbuf);
++ char pbuf[1024], *pend = pbuf + sizeof (pbuf);
+
+ packet.m_nChannel = 0x03; // control channel (invoke)
+ packet.m_headerType = 1; /* RTMP_PACKET_SIZE_MEDIUM; */
+@@ -300,7 +315,7 @@ static int
+ SendPlayStop(RTMP *r)
+ {
+ RTMPPacket packet;
+- char pbuf[512], *pend = pbuf+sizeof(pbuf);
++ char pbuf[1024], *pend = pbuf + sizeof (pbuf);
+
+ packet.m_nChannel = 0x03; // control channel (invoke)
+ packet.m_headerType = 1; /* RTMP_PACKET_SIZE_MEDIUM; */
+@@ -328,6 +343,49 @@ SendPlayStop(RTMP *r)
+ return RTMP_SendPacket(r, &packet, FALSE);
+ }
+
++int
++SendCheckBWResponse(RTMP *r, int oldMethodType, int onBWDoneInit)
++{
++ RTMPPacket packet;
++ char pbuf[256], *pend = pbuf + sizeof (pbuf);
++ char *enc;
++
++ packet.m_nChannel = 0x03; /* control channel (invoke) */
++ packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM;
++ packet.m_packetType = RTMP_PACKET_TYPE_INVOKE;
++ packet.m_nTimeStamp = 0;
++ packet.m_nInfoField2 = 0;
++ packet.m_hasAbsTimestamp = 0;
++ packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE;
++
++ enc = packet.m_body;
++ if (oldMethodType)
++ {
++ enc = AMF_EncodeString(enc, pend, &av__onbwdone);
++ enc = AMF_EncodeNumber(enc, pend, 0);
++ *enc++ = AMF_NULL;
++ enc = AMF_EncodeNumber(enc, pend, 10240);
++ enc = AMF_EncodeNumber(enc, pend, 10240);
++ }
++ else
++ {
++ enc = AMF_EncodeString(enc, pend, &av_onBWDone);
++ enc = AMF_EncodeNumber(enc, pend, 0);
++ *enc++ = AMF_NULL;
++ if (!onBWDoneInit)
++ {
++ enc = AMF_EncodeNumber(enc, pend, 10240);
++ enc = AMF_EncodeNumber(enc, pend, 10240);
++ enc = AMF_EncodeNumber(enc, pend, 0);
++ enc = AMF_EncodeNumber(enc, pend, 0);
++ }
++ }
++
++ packet.m_nBodySize = enc - packet.m_body;
++
++ return RTMP_SendPacket(r, &packet, FALSE);
++}
++
+ static void
+ spawn_dumper(int argc, AVal *av, char *cmd)
+ {
+@@ -568,6 +626,7 @@ ServeInvoke(STREAMING_SERVER *server, RTMP * r, RTMPPacket *packet, unsigned int
+ server->arglen += countAMF(&r->Link.extras, &server->argc);
+ }
+ SendConnectResult(r, txn);
++ SendCheckBWResponse(r, FALSE, TRUE);
+ }
+ else if (AVMATCH(&method, &av_createStream))
+ {
+@@ -582,10 +641,22 @@ ServeInvoke(STREAMING_SERVER *server, RTMP * r, RTMPPacket *packet, unsigned int
+ AVal usherToken;
+ AMFProp_GetString(AMF_GetProp(&obj, NULL, 3), &usherToken);
+ AVreplace(&usherToken, &av_dquote, &av_escdquote);
++#ifdef WIN32
++ AVreplace(&usherToken, &av_caret, &av_esccaret);
++ AVreplace(&usherToken, &av_pipe, &av_escpipe);
++#endif
+ server->arglen += 6 + usherToken.av_len;
+ server->argc += 2;
+ r->Link.usherToken = usherToken;
+ }
++ else if (AVMATCH(&method, &av__checkbw))
++ {
++ SendCheckBWResponse(r, TRUE, FALSE);
++ }
++ else if (AVMATCH(&method, &av_checkBandwidth))
++ {
++ SendCheckBWResponse(r, FALSE, FALSE);
++ }
+ else if (AVMATCH(&method, &av_play))
+ {
+ char *file, *p, *q, *cmd, *ptr;
+@@ -599,6 +670,17 @@ ServeInvoke(STREAMING_SERVER *server, RTMP * r, RTMPPacket *packet, unsigned int
+ if (obj.o_num > 5)
+ r->Link.length = AMFProp_GetNumber(AMF_GetProp(&obj, NULL, 5));
+ */
++ double StartFlag = 0;
++ AMFObjectProperty *Start = AMF_GetProp(&obj, NULL, 4);
++ if (!(Start->p_type == AMF_INVALID))
++ StartFlag = AMFProp_GetNumber(Start);
++ r->Link.app = AVcopy(r->Link.app);
++ if (StartFlag == -1000 || strstr(r->Link.app.av_val, "live"))
++ {
++ StartFlag = -1000;
++ server->arglen += 7;
++ server->argc += 1;
++ }
+ if (r->Link.tcUrl.av_len)
+ {
+ len = server->arglen + r->Link.playpath.av_len + 4 +
+@@ -616,6 +698,7 @@ ServeInvoke(STREAMING_SERVER *server, RTMP * r, RTMPPacket *packet, unsigned int
+ argv[argc].av_val = ptr + 1;
+ argv[argc++].av_len = 2;
+ argv[argc].av_val = ptr + 5;
++ r->Link.tcUrl = StripParams(&r->Link.tcUrl);
+ ptr += sprintf(ptr," -r \"%s\"", r->Link.tcUrl.av_val);
+ argv[argc++].av_len = r->Link.tcUrl.av_len;
+
+@@ -640,6 +723,7 @@ ServeInvoke(STREAMING_SERVER *server, RTMP * r, RTMPPacket *packet, unsigned int
+ argv[argc].av_val = ptr + 1;
+ argv[argc++].av_len = 2;
+ argv[argc].av_val = ptr + 5;
++ r->Link.swfUrl = StripParams(&r->Link.swfUrl);
+ ptr += sprintf(ptr, " -W \"%s\"", r->Link.swfUrl.av_val);
+ argv[argc++].av_len = r->Link.swfUrl.av_len;
+ }
+@@ -662,10 +746,17 @@ ServeInvoke(STREAMING_SERVER *server, RTMP * r, RTMPPacket *packet, unsigned int
+ r->Link.usherToken.av_val = NULL;
+ r->Link.usherToken.av_len = 0;
+ }
+- if (r->Link.extras.o_num) {
+- ptr = dumpAMF(&r->Link.extras, ptr, argv, &argc);
+- AMF_Reset(&r->Link.extras);
+- }
++ if (StartFlag == -1000)
++ {
++ argv[argc].av_val = ptr + 1;
++ argv[argc++].av_len = 6;
++ ptr += sprintf(ptr, " --live");
++ }
++ if (r->Link.extras.o_num)
++ {
++ ptr = dumpAMF(&r->Link.extras, ptr, argv, &argc);
++ AMF_Reset(&r->Link.extras);
++ }
+ argv[argc].av_val = ptr + 1;
+ argv[argc++].av_len = 2;
+ argv[argc].av_val = ptr + 5;
+@@ -673,7 +764,13 @@ ServeInvoke(STREAMING_SERVER *server, RTMP * r, RTMPPacket *packet, unsigned int
+ r->Link.playpath.av_len, r->Link.playpath.av_val);
+ argv[argc++].av_len = r->Link.playpath.av_len;
+
+- av = r->Link.playpath;
++ if (r->Link.playpath.av_len)
++ av = r->Link.playpath;
++ else
++ {
++ av.av_val = "file";
++ av.av_len = 4;
++ }
+ /* strip trailing URL parameters */
+ q = memchr(av.av_val, '?', av.av_len);
+ if (q)
+@@ -725,7 +822,30 @@ ServeInvoke(STREAMING_SERVER *server, RTMP * r, RTMPPacket *packet, unsigned int
+ argv[argc++].av_len = 2;
+ argv[argc].av_val = file;
+ argv[argc].av_len = av.av_len;
+- ptr += sprintf(ptr, " -o %s", file);
++#ifdef VLC
++ char *vlc;
++ int didAlloc = FALSE;
++
++ if (getenv("VLC"))
++ vlc = getenv("VLC");
++ else if (getenv("ProgramFiles"))
++ {
++ vlc = malloc(512 * sizeof (char));
++ didAlloc = TRUE;
++ char *ProgramFiles = getenv("ProgramFiles");
++ sprintf(vlc, "%s%s", ProgramFiles, " (x86)\\VideoLAN\\VLC\\vlc.exe");
++ if (!file_exists(vlc))
++ sprintf(vlc, "%s%s", ProgramFiles, "\\VideoLAN\\VLC\\vlc.exe");
++ }
++ else
++ vlc = "C:\\Program Files\\VideoLAN\\VLC\\vlc.exe";
++
++ ptr += sprintf(ptr, " | %s -", vlc);
++ if (didAlloc)
++ free(vlc);
++#else
++ ptr += sprintf(ptr, " -o %s", file);
++#endif
+ now = RTMP_GetTime();
+ if (now - server->filetime < DUPTIME && AVMATCH(&argv[argc], &server->filename))
+ {
+@@ -739,7 +859,23 @@ ServeInvoke(STREAMING_SERVER *server, RTMP * r, RTMPPacket *packet, unsigned int
+ server->filetime = now;
+ free(server->filename.av_val);
+ server->filename = argv[argc++];
+- spawn_dumper(argc, argv, cmd);
++#ifdef VLC
++ FILE *vlc_cmdfile = fopen("VLC.bat", "w");
++ char *vlc_batchcmd = strreplace(cmd, 0, "%", "%%");
++ fprintf(vlc_cmdfile, "%s\n", vlc_batchcmd);
++ fclose(vlc_cmdfile);
++ free(vlc_batchcmd);
++ spawn_dumper(argc, argv, "VLC.bat");
++#else
++ spawn_dumper(argc, argv, cmd);
++#endif
++
++#ifdef WIN32
++ // Dump command to batch file
++ FILE *cmdfile = fopen("Command.bat", "a");
++ fprintf(cmdfile, "%s\n", cmd);
++ fclose(cmdfile);
++#endif
+ }
+
+ free(cmd);
+@@ -1178,3 +1314,115 @@ AVreplace(AVal *src, const AVal *orig, const AVal *repl)
+ src->av_val = dest;
+ src->av_len = dptr - dest;
+ }
++
++char *
++strreplace(char *srcstr, int srclen, char *orig, char *repl)
++{
++ char *ptr = NULL, *sptr = srcstr;
++ int origlen = strlen(orig);
++ int repllen = strlen(repl);
++ if (!srclen)
++ srclen = strlen(srcstr);
++ char *srcend = srcstr + srclen;
++ int dstbuffer = srclen / origlen * repllen;
++ if (dstbuffer < srclen)
++ dstbuffer = srclen;
++ char *dststr = calloc(dstbuffer + 1, sizeof (char));
++ char *dptr = dststr;
++
++ if ((ptr = strstr(srcstr, orig)))
++ {
++ while (ptr < srcend && (ptr = strstr(sptr, orig)))
++ {
++ int len = ptr - sptr;
++ memcpy(dptr, sptr, len);
++ sptr += len + origlen;
++ dptr += len;
++ memcpy(dptr, repl, repllen);
++ dptr += repllen;
++ }
++ memcpy(dptr, sptr, srcend - sptr);
++ return dststr;
++ }
++
++ memcpy(dststr, srcstr, srclen);
++ return dststr;
++}
++
++AVal
++StripParams(AVal *src)
++{
++ AVal str;
++ if (src->av_val)
++ {
++ str.av_val = calloc(src->av_len + 1, sizeof (char));
++ strncpy(str.av_val, src->av_val, src->av_len);
++ str.av_len = src->av_len;
++ char *start = str.av_val;
++ char *end = start + str.av_len;
++ char *ptr = start;
++
++ while (ptr < end)
++ {
++ if (*ptr == '?')
++ {
++ str.av_len = ptr - start;
++ break;
++ }
++ ptr++;
++ }
++ memset(start + str.av_len, 0, 1);
++
++ char *dynamic = strstr(start, "[[DYNAMIC]]");
++ if (dynamic)
++ {
++ dynamic -= 1;
++ memset(dynamic, 0, 1);
++ str.av_len = dynamic - start;
++ end = start + str.av_len;
++ }
++
++ char *import = strstr(start, "[[IMPORT]]");
++ if (import)
++ {
++ str.av_val = import + 11;
++ strcpy(start, "http://");
++ str.av_val = strcat(start, str.av_val);
++ str.av_len = strlen(str.av_val);
++ }
++ return str;
++ }
++ str = *src;
++ return str;
++}
++
++int
++file_exists(const char *fname)
++{
++ FILE *file;
++ if ((file = fopen(fname, "r")))
++ {
++ fclose(file);
++ return TRUE;
++ }
++ return FALSE;
++}
++
++AVal
++AVcopy(AVal src)
++{
++ AVal dst;
++ if (src.av_len)
++ {
++ dst.av_val = malloc(src.av_len + 1);
++ memcpy(dst.av_val, src.av_val, src.av_len);
++ dst.av_val[src.av_len] = '\0';
++ dst.av_len = src.av_len;
++ }
++ else
++ {
++ dst.av_val = NULL;
++ dst.av_len = 0;
++ }
++ return dst;
++}
+diff --git a/rtmpsuck.c b/rtmpsuck.c
+index e886179..e80c686 100644
+--- a/rtmpsuck.c
++++ b/rtmpsuck.c
+@@ -143,15 +143,18 @@ SAVC(onStatus);
+ SAVC(close);
+ static const AVal av_NetStream_Failed = AVC("NetStream.Failed");
+ static const AVal av_NetStream_Play_Failed = AVC("NetStream.Play.Failed");
+-static const AVal av_NetStream_Play_StreamNotFound =
+-AVC("NetStream.Play.StreamNotFound");
+-static const AVal av_NetConnection_Connect_InvalidApp =
+-AVC("NetConnection.Connect.InvalidApp");
++static const AVal av_NetStream_Play_StreamNotFound = AVC("NetStream.Play.StreamNotFound");
++static const AVal av_NetConnection_Connect_InvalidApp = AVC("NetConnection.Connect.InvalidApp");
+ static const AVal av_NetStream_Play_Start = AVC("NetStream.Play.Start");
+ static const AVal av_NetStream_Play_Complete = AVC("NetStream.Play.Complete");
+ static const AVal av_NetStream_Play_Stop = AVC("NetStream.Play.Stop");
++static const AVal av_NetStream_Authenticate_UsherToken = AVC("NetStream.Authenticate.UsherToken");
+
+ static const char *cst[] = { "client", "server" };
++char *dumpAMF(AMFObject *obj, char *ptr);
++char *strreplace(char *srcstr, int srclen, char *orig, char *repl);
++AVal AVcopy(AVal src);
++AVal StripParams(AVal *src);
+
+ // Returns 0 for OK/Failed/error, 1 for 'Stop or Complete'
+ int
+@@ -198,26 +201,28 @@ ServeInvoke(STREAMING_SERVER *server, int which, RTMPPacket *pack, const char *b
+ if (cobj.o_props[i].p_type == AMF_STRING)
+ {
+ pval = cobj.o_props[i].p_vu.p_aval;
+- RTMP_LogPrintf("%.*s: %.*s\n", pname.av_len, pname.av_val, pval.av_len, pval.av_val);
++ RTMP_LogPrintf("%10.*s : %.*s\n", pname.av_len, pname.av_val, pval.av_len, pval.av_val);
+ }
+ if (AVMATCH(&pname, &av_app))
+ {
+- server->rc.Link.app = pval;
++ server->rc.Link.app = AVcopy(pval);
+ pval.av_val = NULL;
+ }
+ else if (AVMATCH(&pname, &av_flashVer))
+ {
+- server->rc.Link.flashVer = pval;
++ server->rc.Link.flashVer = AVcopy(pval);
+ pval.av_val = NULL;
+ }
+ else if (AVMATCH(&pname, &av_swfUrl))
+ {
+ #ifdef CRYPTO
+ if (pval.av_val)
+- RTMP_HashSWF(pval.av_val, &server->rc.Link.SWFSize,
+- (unsigned char *)server->rc.Link.SWFHash, 30);
++ {
++ AVal swfUrl = StripParams(&pval);
++ RTMP_HashSWF(swfUrl.av_val, &server->rc.Link.SWFSize, (unsigned char *) server->rc.Link.SWFHash, 30);
++ }
+ #endif
+- server->rc.Link.swfUrl = pval;
++ server->rc.Link.swfUrl = AVcopy(pval);
+ pval.av_val = NULL;
+ }
+ else if (AVMATCH(&pname, &av_tcUrl))
+@@ -225,7 +230,7 @@ ServeInvoke(STREAMING_SERVER *server, int which, RTMPPacket *pack, const char *b
+ char *r1 = NULL, *r2;
+ int len;
+
+- server->rc.Link.tcUrl = pval;
++ server->rc.Link.tcUrl = AVcopy(pval);
+ if ((pval.av_val[0] | 0x40) == 'r' &&
+ (pval.av_val[1] | 0x40) == 't' &&
+ (pval.av_val[2] | 0x40) == 'm' &&
+@@ -267,7 +272,7 @@ ServeInvoke(STREAMING_SERVER *server, int which, RTMPPacket *pack, const char *b
+ }
+ else if (AVMATCH(&pname, &av_pageUrl))
+ {
+- server->rc.Link.pageUrl = pval;
++ server->rc.Link.pageUrl = AVcopy(pval);
+ pval.av_val = NULL;
+ }
+ else if (AVMATCH(&pname, &av_audioCodecs))
+@@ -287,14 +292,21 @@ ServeInvoke(STREAMING_SERVER *server, int which, RTMPPacket *pack, const char *b
+ if (pval.av_val)
+ free(pval.av_val);
+ }
++
+ if (obj.o_num > 3)
+ {
+- if (AMFProp_GetBoolean(&obj.o_props[3]))
+- server->rc.Link.lFlags |= RTMP_LF_AUTH;
+- if (obj.o_num > 4)
+- {
+- AMFProp_GetString(&obj.o_props[4], &server->rc.Link.auth);
+- }
++ int i = obj.o_num - 3;
++ server->rc.Link.extras.o_num = i;
++ server->rc.Link.extras.o_props = malloc(i * sizeof (AMFObjectProperty));
++ memcpy(server->rc.Link.extras.o_props, obj.o_props + 3, i * sizeof (AMFObjectProperty));
++ obj.o_num = 3;
++ }
++
++ if (server->rc.Link.extras.o_num)
++ {
++ server->rc.Link.Extras.av_val = calloc(1024, sizeof (char));
++ dumpAMF(&server->rc.Link.extras, server->rc.Link.Extras.av_val);
++ server->rc.Link.Extras.av_len = strlen(server->rc.Link.Extras.av_val);
+ }
+
+ if (!RTMP_Connect(&server->rc, pack))
+@@ -303,6 +315,16 @@ ServeInvoke(STREAMING_SERVER *server, int which, RTMPPacket *pack, const char *b
+ return 1;
+ }
+ server->rc.m_bSendCounter = FALSE;
++
++ if (server->rc.Link.extras.o_props)
++ {
++ AMF_Reset(&server->rc.Link.extras);
++ }
++ }
++ else if (AVMATCH(&method, &av_NetStream_Authenticate_UsherToken))
++ {
++ AMFProp_GetString(AMF_GetProp(&obj, NULL, 3), &server->rc.Link.usherToken);
++ RTMP_LogPrintf("%10s : %.*s\n", "usherToken", server->rc.Link.usherToken.av_len, server->rc.Link.usherToken.av_val);
+ }
+ else if (AVMATCH(&method, &av_play))
+ {
+@@ -323,6 +345,14 @@ ServeInvoke(STREAMING_SERVER *server, int which, RTMPPacket *pack, const char *b
+ if (!av.av_val)
+ goto out;
+
++ double StartFlag = 0;
++ AMFObjectProperty *Start = AMF_GetProp(&obj, NULL, 4);
++ if (!(Start->p_type == AMF_INVALID))
++ StartFlag = AMFProp_GetNumber(Start);
++ if (StartFlag == -1000 || strstr(server->rc.Link.app.av_val, "live"))
++ StartFlag = -1000;
++ RTMP_LogPrintf("%10s : %s\n", "live", (StartFlag == -1000) ? "yes" : "no");
++
+ /* check for duplicates */
+ for (fl = server->f_head; fl; fl=fl->f_next)
+ {
+@@ -372,9 +402,51 @@ ServeInvoke(STREAMING_SERVER *server, int which, RTMPPacket *pack, const char *b
+ for (p=file; *p; p++)
+ if (*p == ':')
+ *p = '_';
+- RTMP_LogPrintf("Playpath: %.*s\nSaving as: %s\n",
+- server->rc.Link.playpath.av_len, server->rc.Link.playpath.av_val,
+- file);
++ RTMP_LogPrintf("%10s : %.*s\n%10s : %s\n", "Playpath", server->rc.Link.playpath.av_len,
++ server->rc.Link.playpath.av_val, "Saving as", file);
++
++#ifdef WIN32
++ // Dump command to batch file
++ char *cmd = NULL, *ptr = NULL;
++ AVal swfUrl, tcUrl;
++
++ cmd = calloc(2048, sizeof (char));
++ ptr = cmd;
++ tcUrl = StripParams(&server->rc.Link.tcUrl);
++ swfUrl = StripParams(&server->rc.Link.swfUrl);
++ ptr += sprintf(ptr, "rtmpdump -r \"%.*s\" -a \"%.*s\" -f \"%.*s\" -W \"%.*s\" -p \"%.*s\"",
++ tcUrl.av_len, tcUrl.av_val,
++ server->rc.Link.app.av_len, server->rc.Link.app.av_val,
++ server->rc.Link.flashVer.av_len, server->rc.Link.flashVer.av_val,
++ swfUrl.av_len, swfUrl.av_val,
++ server->rc.Link.pageUrl.av_len, server->rc.Link.pageUrl.av_val);
++
++ if (server->rc.Link.usherToken.av_val)
++ {
++ char *usherToken = strreplace(server->rc.Link.usherToken.av_val, server->rc.Link.usherToken.av_len, "\"", "\\\"");
++ usherToken = strreplace(usherToken, 0, "^", "^^");
++ usherToken = strreplace(usherToken, 0, "|", "^|");
++ ptr += sprintf(ptr, " --jtv \"%s\"", usherToken);
++ free(usherToken);
++ }
++
++ if (server->rc.Link.Extras.av_len)
++ {
++ ptr += sprintf(ptr, "%.*s", server->rc.Link.Extras.av_len, server->rc.Link.Extras.av_val);
++ }
++
++ if (StartFlag == -1000)
++ ptr += sprintf(ptr, "%s", " --live");
++ ptr += sprintf(ptr, " -y \"%.*s\"", server->rc.Link.playpath.av_len, server->rc.Link.playpath.av_val);
++ ptr += sprintf(ptr, " -o \"%s.flv\"\n", file);
++
++ FILE *cmdfile = fopen("Command.bat", "a");
++ fprintf(cmdfile, "%s", cmd);
++ fclose(cmdfile);
++
++ free(cmd);
++#endif
++
+ out = fopen(file, "wb");
+ free(file);
+ if (!out)
+@@ -1196,3 +1268,146 @@ main(int argc, char **argv)
+ #endif
+ return nStatus;
+ }
++
++char *
++dumpAMF(AMFObject *obj, char *ptr)
++{
++ int i;
++ const char opt[] = "NBSO Z";
++
++ for (i = 0; i < obj->o_num; i++)
++ {
++ AMFObjectProperty *p = &obj->o_props[i];
++ if (p->p_type > 5)
++ continue;
++ ptr += sprintf(ptr, " -C ");
++ if (p->p_name.av_val)
++ *ptr++ = 'N';
++ *ptr++ = opt[p->p_type];
++ *ptr++ = ':';
++ if (p->p_name.av_val)
++ ptr += sprintf(ptr, "%.*s:", p->p_name.av_len, p->p_name.av_val);
++ switch (p->p_type)
++ {
++ case AMF_BOOLEAN:
++ *ptr++ = p->p_vu.p_number != 0 ? '1' : '0';
++ break;
++ case AMF_STRING:
++ memcpy(ptr, p->p_vu.p_aval.av_val, p->p_vu.p_aval.av_len);
++ ptr += p->p_vu.p_aval.av_len;
++ break;
++ case AMF_NUMBER:
++ ptr += sprintf(ptr, "%f", p->p_vu.p_number);
++ break;
++ case AMF_OBJECT:
++ *ptr++ = '1';
++ ptr = dumpAMF(&p->p_vu.p_object, ptr);
++ ptr += sprintf(ptr, " -C O:0");
++ break;
++ case AMF_NULL:
++ default:
++ break;
++ }
++ }
++ return ptr;
++}
++
++char *
++strreplace(char *srcstr, int srclen, char *orig, char *repl)
++{
++ char *ptr = NULL, *sptr = srcstr;
++ int origlen = strlen(orig);
++ int repllen = strlen(repl);
++ if (!srclen)
++ srclen = strlen(srcstr);
++ char *srcend = srcstr + srclen;
++ int dstbuffer = srclen / origlen * repllen;
++ if (dstbuffer < srclen)
++ dstbuffer = srclen;
++ char *dststr = calloc(dstbuffer + 1, sizeof (char));
++ char *dptr = dststr;
++
++ if ((ptr = strstr(srcstr, orig)))
++ {
++ while (ptr < srcend && (ptr = strstr(sptr, orig)))
++ {
++ int len = ptr - sptr;
++ memcpy(dptr, sptr, len);
++ sptr += len + origlen;
++ dptr += len;
++ memcpy(dptr, repl, repllen);
++ dptr += repllen;
++ }
++ memcpy(dptr, sptr, srcend - sptr);
++ return dststr;
++ }
++
++ memcpy(dststr, srcstr, srclen);
++ return dststr;
++}
++
++AVal
++StripParams(AVal *src)
++{
++ AVal str;
++ if (src->av_val)
++ {
++ str.av_val = calloc(src->av_len + 1, sizeof (char));
++ strncpy(str.av_val, src->av_val, src->av_len);
++ str.av_len = src->av_len;
++ char *start = str.av_val;
++ char *end = start + str.av_len;
++ char *ptr = start;
++
++ while (ptr < end)
++ {
++ if (*ptr == '?')
++ {
++ str.av_len = ptr - start;
++ break;
++ }
++ ptr++;
++ }
++ memset(start + str.av_len, 0, 1);
++
++ char *dynamic = strstr(start, "[[DYNAMIC]]");
++ if (dynamic)
++ {
++ dynamic -= 1;
++ memset(dynamic, 0, 1);
++ str.av_len = dynamic - start;
++ end = start + str.av_len;
++ }
++
++ char *import = strstr(start, "[[IMPORT]]");
++ if (import)
++ {
++ str.av_val = import + 11;
++ strcpy(start, "http://");
++ str.av_val = strcat(start, str.av_val);
++ str.av_len = strlen(str.av_val);
++ }
++ return str;
++ }
++ str = *src;
++ return str;
++}
++
++AVal
++AVcopy(AVal src)
++{
++ AVal dst;
++ if (src.av_len)
++ {
++ dst.av_val = malloc(src.av_len + 1);
++ memcpy(dst.av_val, src.av_val, src.av_len);
++ dst.av_val[src.av_len] = '\0';
++ dst.av_len = src.av_len;
++ }
++ else
++ {
++ dst.av_val = NULL;
++ dst.av_len = 0;
++ }
++ return dst;
++}
DEPS= ../Makefile.include Makefile
LIBNAME=xbmc-pvr-addons
-VERSION=5f97406cffb412ac5161c3dc51205caca009fcc7
+VERSION=96774c4f775b156a46fb58151379dece3e773c96
GIT_DIR=$(TARBALLS_LOCATION)/$(LIBNAME).git
BASE_URL=git://github.com/opdenkamp/$(LIBNAME).git
DYLIB=$(PLATFORM)/addons/pvr.demo/.libs/libpvrdemo-addon.so
# lib name, version
LIBNAME=xbmc-pvr-addons
-VERSION=5f97406cffb412ac5161c3dc51205caca009fcc7
+VERSION=96774c4f775b156a46fb58151379dece3e773c96
SOURCE=$(LIBNAME)-$(VERSION)
ARCHIVE=$(SOURCE).tar.gz
#include "input/SDLJoystick.h"
#endif
+#if defined(TARGET_ANDROID)
+#include "android/activity/XBMCApp.h"
+#endif
+
using namespace std;
using namespace ADDON;
using namespace XFILE;
CLog::Log(LOGNOTICE, "Starting XBMC (%s), Platform: Linux (%s, %s). Built on %s", g_infoManager.GetVersion().c_str(), g_sysinfo.GetLinuxDistro().c_str(), g_sysinfo.GetUnameVersion().c_str(), __DATE__);
#elif defined(_WIN32)
CLog::Log(LOGNOTICE, "Starting XBMC (%s), Platform: %s. Built on %s (compiler %i)", g_infoManager.GetVersion().c_str(), g_sysinfo.GetKernelVersion().c_str(), __DATE__, _MSC_VER);
+ CLog::Log(LOGNOTICE, g_cpuInfo.getCPUModel().c_str());
+ CLog::Log(LOGNOTICE, CWIN32Util::GetResInfoString());
+ CLog::Log(LOGNOTICE, "Running with %s rights", (CWIN32Util::IsCurrentUserLocalAdministrator() == TRUE) ? "administrator" : "restricted");
+ CLog::Log(LOGNOTICE, "Aero is %s", (g_sysinfo.IsAeroDisabled() == true) ? "disabled" : "enabled");
+#endif
#if defined(__arm__)
if (g_cpuInfo.GetCPUFeatures() & CPU_FEATURE_NEON)
CLog::Log(LOGNOTICE, "ARM Features: Neon enabled");
else
CLog::Log(LOGNOTICE, "ARM Features: Neon disabled");
#endif
- CLog::Log(LOGNOTICE, g_cpuInfo.getCPUModel().c_str());
- CLog::Log(LOGNOTICE, CWIN32Util::GetResInfoString());
- CLog::Log(LOGNOTICE, "Running with %s rights", (CWIN32Util::IsCurrentUserLocalAdministrator() == TRUE) ? "administrator" : "restricted");
- CLog::Log(LOGNOTICE, "Aero is %s", (g_sysinfo.IsAeroDisabled() == true) ? "disabled" : "enabled");
-#endif
CSpecialProtocol::LogPaths();
CStdString executable = CUtil::ResolveExecutablePath();
if (g_settings.m_bMute)
UnMute();
float volume = g_settings.m_fVolumeLevel;
+// Android has steps based on the max available volume level
+#if defined(TARGET_ANDROID)
+ float step = (VOLUME_MAXIMUM - VOLUME_MINIMUM) / CXBMCApp::GetMaxSystemVolume();
+#else
float step = (VOLUME_MAXIMUM - VOLUME_MINIMUM) / VOLUME_CONTROL_STEPS;
+
if (action.GetRepeat())
step *= action.GetRepeat() * 50; // 50 fps
-
+#endif
if (action.GetID() == ACTION_VOLUME_UP)
volume += (float)fabs(action.GetAmount()) * action.GetAmount() * step;
else
volume -= (float)fabs(action.GetAmount()) * action.GetAmount() * step;
-
SetVolume(volume, false);
}
// show visual feedback of volume change...
SetPlaySpeed(iSpeed);
}
+ // if player has volume control, set it.
+ if (m_pPlayer && m_pPlayer->ControlsVolume())
+ {
+ m_pPlayer->SetVolume(g_settings.m_fVolumeLevel);
+ m_pPlayer->SetMute(g_settings.m_bMute);
+ }
+
if( IsPlayingAudio() )
{
if (g_windowManager.GetActiveWindow() == WINDOW_FULLSCREEN_VIDEO)
value = 1.0f;
CAEFactory::SetVolume(value);
-
- /* for platforms where we do not have AE */
- if (!CAEFactory::GetEngine() && m_pPlayer)
- m_pPlayer->SetVolume(g_settings.m_fVolumeLevel);
}
int CApplication::GetVolume() const
data["volume"] = GetVolume();
data["muted"] = g_settings.m_bMute;
CAnnouncementManager::Announce(Application, "xbmc", "OnVolumeChanged", data);
+
+ // if player has volume control, set it.
+ if (m_pPlayer && m_pPlayer->ControlsVolume())
+ {
+ m_pPlayer->SetVolume(g_settings.m_fVolumeLevel);
+ m_pPlayer->SetMute(g_settings.m_bMute);
+ }
}
int CApplication::GetSubtitleDelay() const
m_iPlaySpeed = iSpeed;
m_pPlayer->ToFFRW(m_iPlaySpeed);
- if (m_iPlaySpeed == 1)
- { // restore volume
- m_pPlayer->SetVolume(VOLUME_MAXIMUM);
- }
- else
- { // mute volume
- m_pPlayer->SetVolume(VOLUME_MINIMUM);
+
+ // if player has volume control, set it.
+ if (m_pPlayer->ControlsVolume())
+ {
+ if (m_iPlaySpeed == 1)
+ { // restore volume
+ m_pPlayer->SetVolume(VOLUME_MAXIMUM);
+ }
+ else
+ { // mute volume
+ m_pPlayer->SetVolume(VOLUME_MINIMUM);
+ }
+ m_pPlayer->SetMute(g_settings.m_bMute);
}
}
// timer information
#ifdef _WIN32
CWinIdleTimer m_idleTimer;
+ CWinIdleTimer m_screenSaverTimer;
#else
CStopWatch m_idleTimer;
+ CStopWatch m_screenSaverTimer;
#endif
CStopWatch m_restartPlayerTimer;
CStopWatch m_frameTime;
CStopWatch m_navigationTimer;
CStopWatch m_slowTimer;
- CStopWatch m_screenSaverTimer;
CStopWatch m_shutdownTimer;
bool m_bInhibitIdleShutdown;
case TMSG_MEDIA_STOP:
{
// restore to previous window if needed
- if (g_windowManager.GetActiveWindow() == WINDOW_SLIDESHOW ||
- g_windowManager.GetActiveWindow() == WINDOW_FULLSCREEN_VIDEO ||
- g_windowManager.GetActiveWindow() == WINDOW_VISUALISATION)
+ bool stopSlideshow = true;
+ bool stopVideo = true;
+ bool stopMusic = true;
+ if (pMsg->dwParam1 >= 0)
+ {
+ stopSlideshow = (pMsg->dwParam1 == PLAYLIST_PICTURE);
+ stopVideo = (pMsg->dwParam1 == PLAYLIST_VIDEO);
+ stopMusic = (pMsg->dwParam1 == PLAYLIST_MUSIC);
+ }
+
+ if ((stopSlideshow && g_windowManager.GetActiveWindow() == WINDOW_SLIDESHOW) ||
+ (stopVideo && g_windowManager.GetActiveWindow() == WINDOW_FULLSCREEN_VIDEO) ||
+ (stopMusic && g_windowManager.GetActiveWindow() == WINDOW_VISUALISATION))
g_windowManager.PreviousWindow();
g_application.ResetScreenSaver();
SendMessage(tMsg, false);
}
-void CApplicationMessenger::MediaStop(bool bWait /* = true */)
+void CApplicationMessenger::MediaStop(bool bWait /* = true */, int playlistid /* = -1 */)
{
ThreadMessage tMsg = {TMSG_MEDIA_STOP};
+ tMsg.dwParam1 = playlistid;
SendMessage(tMsg, bWait);
}
void MediaPlay(const CFileItem &item);
void MediaPlay(const CFileItemList &item, int song = 0);
void MediaPlay(int playlistid, int song = -1);
- void MediaStop(bool bWait = true);
+ void MediaStop(bool bWait = true, int playlistid = -1);
void MediaPause();
void MediaRestart(bool bWait);
useFolder = true;
strFile = GetLocalMetadataPath();
}
- else if (useFolder)
+ else if (useFolder && !(m_bIsFolder && !IsFileFolder()))
strFile = URIUtils::GetDirectory(strFile);
if (strFile.empty()) // empty filepath -> nothing to find
{"xbmc.gui.skin", ADDON_SKIN, 166, "DefaultAddonSkin.png" },
{"xbmc.gui.webinterface", ADDON_WEB_INTERFACE, 199, "DefaultAddonWebSkin.png" },
{"xbmc.addon.repository", ADDON_REPOSITORY, 24011, "DefaultAddonRepository.png" },
- {"xbmc.pvrclient", ADDON_PVRDLL, 24019, "" },
+ {"xbmc.pvrclient", ADDON_PVRDLL, 24019, "DefaultAddonPVRClient.png" },
{"xbmc.addon.video", ADDON_VIDEO, 1037, "DefaultAddonVideo.png" },
{"xbmc.addon.audio", ADDON_AUDIO, 1038, "DefaultAddonMusic.png" },
{"xbmc.addon.image", ADDON_IMAGE, 1039, "DefaultAddonPicture.png" },
if (disable)
{
- CStdString sql = PrepareSQL("select id from disabled where addonID='%s'", addonID.c_str());
- m_pDS->query(sql.c_str());
- if (m_pDS->eof()) // not found
+ if (!IsAddonDisabled(addonID)) // Enabled
{
- m_pDS->close();
- sql = PrepareSQL("insert into disabled(id, addonID) values(NULL, '%s')", addonID.c_str());
+ CStdString sql = PrepareSQL("insert into disabled(id, addonID) values(NULL, '%s')", addonID.c_str());
m_pDS->exec(sql);
AddonPtr addon;
}
else
{
+ bool disabled = IsAddonDisabled(addonID); //we need to know if service addon is running
CStdString sql = PrepareSQL("delete from disabled where addonID='%s'", addonID.c_str());
m_pDS->exec(sql);
AddonPtr addon;
// If the addon is a service, start it
- if (CAddonMgr::Get().GetAddon(addonID, addon, ADDON_SERVICE, false) && addon)
+ if (CAddonMgr::Get().GetAddon(addonID, addon, ADDON_SERVICE, false) && addon && disabled)
{
boost::shared_ptr<CService> service = boost::dynamic_pointer_cast<CService>(addon);
if (service)
if (m_addon->Type() == ADDON_SERVICE)
{
- boost::shared_ptr<CService> service = boost::dynamic_pointer_cast<CService>(m_addon);
+ CAddonDatabase database;
+ database.Open();
+ bool running = !database.IsAddonDisabled(m_addon->ID()); //grab a current state
+ database.DisableAddon(m_addon->ID(),false); // enable it so we can remove it??
+ // regrab from manager to have the correct path set
+ AddonPtr addon;
+ ADDON::CAddonMgr::Get().GetAddon(m_addon->ID(), addon);
+ boost::shared_ptr<CService> service = boost::dynamic_pointer_cast<CService>(addon);
if (service)
service->Stop();
- return true;
+ CAddonMgr::Get().RemoveAddon(m_addon->ID()); // remove it
+ return running;
}
if (m_addon->Type() == ADDON_PVRDLL)
if (m_addon->Type() == ADDON_SERVICE)
{
- // regrab from manager to have the correct path set
- AddonPtr addon;
- CAddonMgr::Get().GetAddon(m_addon->ID(), addon);
- boost::shared_ptr<CService> service = boost::dynamic_pointer_cast<CService>(addon);
- if (service)
- service->Start();
+ CAddonDatabase database;
+ database.Open();
+ database.DisableAddon(m_addon->ID(),!reloadAddon); //return it into state it was before OnPreInstall()
+ if (reloadAddon) // reload/start it if it was running
+ {
+ // regrab from manager to have the correct path set
+ AddonPtr addon;
+ CAddonMgr::Get().GetAddon(m_addon->ID(), addon);
+ boost::shared_ptr<CService> service = boost::dynamic_pointer_cast<CService>(addon);
+ if (service)
+ service->Start();
+ }
}
if (m_addon->Type() == ADDON_REPOSITORY)
// stop the pvr manager, so running pvr add-ons are stopped and closed
PVR::CPVRManager::Get().Stop();
}
-
+ if (m_addon->Type() == ADDON_SERVICE)
+ {
+ boost::shared_ptr<CService> service = boost::dynamic_pointer_cast<CService>(m_addon);
+ if (service)
+ service->Stop();
+ }
if (!CAddonInstallJob::DeleteAddon(m_addon->Path()))
return false;
CStdString path = "special://home/addons/packages/";
path += m_localAddon->ID()+"-"+m_rollbackVersions[choice]+".zip";
// needed as cpluff won't downgrade
- CAddonMgr::Get().RemoveAddon(m_localAddon->ID());
+ if (!m_localAddon->IsType(ADDON_SERVICE))
+ //we will handle this for service addons in CAddonInstallJob::OnPostInstall
+ CAddonMgr::Get().RemoveAddon(m_localAddon->ID());
CAddonInstaller::Get().InstallFromZip(path);
database.RemoveAddonFromBlacklist(m_localAddon->ID(),m_rollbackVersions[choice]);
Close();
default:
break;
}
-
return ActivityOK;
}
usage = fmt.str();
return true;
}
+
+// Used in Application.cpp to figure out volume steps
+int CXBMCApp::GetMaxSystemVolume()
+{
+ static int maxVolume = -1;
+ if (maxVolume == -1)
+ {
+ JNIEnv *env = NULL;
+ AttachCurrentThread(&env);
+ maxVolume = GetMaxSystemVolume(env);
+ DetachCurrentThread();
+ }
+ return maxVolume;
+}
+
+int CXBMCApp::GetMaxSystemVolume(JNIEnv *env)
+{
+ jobject oActivity = m_activity->clazz;
+ jclass cActivity = env->GetObjectClass(oActivity);
+
+ // Get Audio manager
+ // (AudioManager)getSystemService(Context.AUDIO_SERVICE)
+ jmethodID mgetSystemService = env->GetMethodID(cActivity, "getSystemService","(Ljava/lang/String;)Ljava/lang/Object;");
+ jstring sAudioService = env->NewStringUTF("audio");
+ jobject oAudioManager = env->CallObjectMethod(oActivity, mgetSystemService, sAudioService);
+ env->DeleteLocalRef(sAudioService);
+ env->DeleteLocalRef(cActivity);
+
+ // Get max volume
+ // int max_volume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+ jclass cAudioManager = env->GetObjectClass(oAudioManager);
+ jmethodID mgetStreamMaxVolume = env->GetMethodID(cAudioManager, "getStreamMaxVolume", "(I)I");
+ jfieldID fstreamMusic = env->GetStaticFieldID(cAudioManager, "STREAM_MUSIC", "I");
+ jint stream_music = env->GetStaticIntField(cAudioManager, fstreamMusic);
+ int maxVolume = (int)env->CallObjectMethod(oAudioManager, mgetStreamMaxVolume, stream_music); // AudioManager.STREAM_MUSIC
+
+ env->DeleteLocalRef(oAudioManager);
+ env->DeleteLocalRef(cAudioManager);
+
+ return maxVolume;
+}
+
+void CXBMCApp::SetSystemVolume(JNIEnv *env, float percent)
+{
+ CLog::Log(LOGDEBUG, "CXBMCApp::SetSystemVolume: %f", percent);
+
+ jobject oActivity = m_activity->clazz;
+ jclass cActivity = env->GetObjectClass(oActivity);
+
+ // Get Audio manager
+ // (AudioManager)getSystemService(Context.AUDIO_SERVICE)
+ jmethodID mgetSystemService = env->GetMethodID(cActivity, "getSystemService","(Ljava/lang/String;)Ljava/lang/Object;");
+ jstring sAudioService = env->NewStringUTF("audio");
+ jobject oAudioManager = env->CallObjectMethod(oActivity, mgetSystemService, sAudioService);
+ jclass cAudioManager = env->GetObjectClass(oAudioManager);
+ env->DeleteLocalRef(sAudioService);
+ env->DeleteLocalRef(cActivity);
+
+ // Set volume
+ // mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, max_volume, 0);
+ jfieldID fstreamMusic = env->GetStaticFieldID(cAudioManager, "STREAM_MUSIC", "I");
+ jint stream_music = env->GetStaticIntField(cAudioManager, fstreamMusic);
+ jmethodID msetStreamVolume = env->GetMethodID(cAudioManager, "setStreamVolume", "(III)V");
+ env->CallObjectMethod(oAudioManager, msetStreamVolume, stream_music, int(GetMaxSystemVolume(env)*percent), 0);
+ env->DeleteLocalRef(oAudioManager);
+ env->DeleteLocalRef(cAudioManager);
+}
+
static bool ListApplications(std::vector <androidPackage> *applications);
static bool GetIconSize(const std::string &packageName, int *width, int *height);
static bool GetIcon(const std::string &packageName, void* buffer, unsigned int bufSize);
+
/*!
* \brief If external storage is available, it returns the path for the external storage (for the specified type)
* \param path will contain the path of the external storage (for the specified type)
*/
static bool GetExternalStorage(std::string &path, const std::string &type = "");
static bool GetStorageUsage(const std::string &path, std::string &usage);
+ static int GetMaxSystemVolume();
static int GetDPI();
protected:
static int AttachCurrentThread(JNIEnv** p_env, void* thr_args = NULL);
static int DetachCurrentThread();
+ static int GetMaxSystemVolume(JNIEnv *env);
+ static void SetSystemVolume(JNIEnv *env, float percent);
+
private:
static bool HasLaunchIntent(const std::string &package);
bool getWakeLock(JNIEnv *env);
static ANativeActivity *m_activity;
jobject m_wakeLock;
-
typedef enum {
// XBMC_Initialize hasn't been executed yet
Uninitialized,
#include "CoreAudioAEStream.h"
#include "CoreAudioAESound.h"
+#include "CoreAudioHardware.h"
#include "cores/AudioEngine/Utils/AEUtil.h"
#include "settings/GUISettings.h"
#include "settings/Settings.h"
#include "utils/log.h"
#include "utils/TimeUtils.h"
#include "utils/MathUtils.h"
+#include "threads/SystemClock.h"
#define DELAY_FRAME_TIME 20
#define BUFFERSIZE 16416
m_muted (false ),
m_soundMode (AE_SOUND_OFF ),
m_streamsPlaying (false ),
- m_isSuspended (false )
+ m_isSuspended (false ),
+ m_softSuspend (false ),
+ m_softSuspendTimer (0 ),
+ m_currentAudioDevice (0 )
{
HAL = new CCoreAudioAEHAL;
}
(*itt)->Initialize();
streamLock.Leave();
}
+
+ if (m_Initialized)
+ {
+ m_currentAudioDevice = CCoreAudioHardware::FindAudioDevice(m_outputDevice);
+ }
return m_Initialized;
}
IAEStream* CCoreAudioAE::MakeStream(enum AEDataFormat dataFormat,
unsigned int sampleRate, unsigned int encodedSamplerate, CAEChannelInfo channelLayout, unsigned int options)
{
+ bool defaultDeviceChanged = false;
// if we are suspended we don't
// want anyone to mess with us
- if (m_isSuspended)
+ if (m_isSuspended && !m_softSuspend)
return NULL;
CAEChannelInfo channelInfo(channelLayout);
m_streams.push_back(stream);
streamLock.Leave();
+ // check if default device has changed - in that case we need to reinit
+ // TODO hook into osx callbacks for getting notifiaction on device
+ // changes and then queue a change of the default device
+ // to a point where engine is idle.
+ std::string outputDevice = g_guiSettings.GetString("audiooutput.audiodevice");
+ if (outputDevice.compare("CoreAudio:default") == 0)
+ {
+ AudioDeviceID currentId = CCoreAudioHardware::FindAudioDevice("default");
+ if (currentId != m_currentAudioDevice)
+ defaultDeviceChanged = true;
+ }
+
if ((options & AESTREAM_PAUSED) == 0)
Stop();
if (m_Initialized && ( m_lastStreamFormat != dataFormat ||
m_lastChLayoutCount != channelLayout.Count() ||
m_lastSampleRate != sampleRate ||
- COREAUDIO_IS_RAW(dataFormat)))
+ COREAUDIO_IS_RAW(dataFormat) ||
+ defaultDeviceChanged))
{
CSingleLock engineLock(m_engineLock);
Stop();
void CCoreAudioAE::PlaySound(IAESound *sound)
{
- if (m_soundMode == AE_SOUND_OFF || (m_soundMode == AE_SOUND_IDLE && m_streamsPlaying) || m_isSuspended)
+ if (m_soundMode == AE_SOUND_OFF || (m_soundMode == AE_SOUND_IDLE && m_streamsPlaying) || (m_isSuspended && !m_softSuspend))
return;
float *samples = ((CCoreAudioAESound*)sound)->GetSamples();
void CCoreAudioAE::GarbageCollect()
{
+ if (g_advancedSettings.m_streamSilence)
+ return;
+
+ if (!m_streamsPlaying && m_playing_sounds.empty())
+ {
+ if (!m_softSuspend)
+ {
+ m_softSuspend = true;
+ m_softSuspendTimer = XbmcThreads::SystemClockMillis() + 10000; //10.0 second delay for softSuspend
+ }
+ }
+ else
+ {
+ if (m_isSuspended)
+ {
+ CSingleLock engineLock(m_engineLock);
+ CLog::Log(LOGDEBUG, "CCoreAudioAE::GarbageCollect - Acquire CA HAL.");
+ Start();
+ m_isSuspended = false;
+ }
+ m_softSuspend = false;
+ }
+
+ unsigned int curSystemClock = XbmcThreads::SystemClockMillis();
+ if (!m_isSuspended && m_softSuspend && curSystemClock > m_softSuspendTimer)
+ {
+ Suspend();// locks m_engineLock internally
+ CLog::Log(LOGDEBUG, "CCoreAudioAE::GarbageCollect - Release CA HAL.");
+ }
}
void CCoreAudioAE::EnumerateOutputDevices(AEDeviceList &devices, bool passthrough)
{
- if (m_isSuspended)
+ if (m_isSuspended && !m_softSuspend)
return;
HAL->EnumerateOutputDevices(devices, passthrough);
bool CCoreAudioAE::Suspend()
{
+ CSingleLock engineLock(m_engineLock);
CLog::Log(LOGDEBUG, "CCoreAudioAE::Suspend - Suspending AE processing");
m_isSuspended = true;
// stop all gui sounds
int m_soundMode;
bool m_streamsPlaying;
bool m_isSuspended;
+ bool m_softSuspend;
+ unsigned int m_softSuspendTimer;
+ AudioDeviceID m_currentAudioDevice;
};
CoreAudioDeviceList deviceList;
CCoreAudioHardware::GetOutputDevices(&deviceList);
- std::string defaultDeviceName;
- CCoreAudioHardware::GetOutputDeviceName(defaultDeviceName);
+ devices.push_back(AEDevice("Default", "CoreAudio:default"));
std::string deviceName;
for (int i = 0; !deviceList.empty(); i++)
// even if setting hogmode was successfull our PID might not get written
// into m_HogPid (so it stays -1). Readback hogstatus for judging if we
// had success on getting hog status
- m_HogPid = GetHogStatus();
+ // We do this only when AudioObjectSetPropertyData didn't set m_HogPid because
+ // it seems that in the other cases the GetHogStatus could return -1
+ // which would overwrite our valid m_HogPid again
+ // Man we should never touch this shit again ;)
+ if (m_HogPid == -1)
+ m_HogPid = GetHogStatus();
if (ret || m_HogPid != getpid())
{
#include "CoreAudioAEHAL.h"
#include "utils/log.h"
+#include "osx/DarwinUtils.h"
bool CCoreAudioHardware::GetAutoHogMode()
{
AudioDeviceID CCoreAudioHardware::GetDefaultOutputDevice()
{
+ AudioDeviceID deviceId = 0;
+ static AudioDeviceID lastDeviceId = 0;
+
AudioObjectPropertyAddress propertyAddress;
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
-
- AudioDeviceID deviceId = 0;
+
UInt32 size = sizeof(AudioDeviceID);
OSStatus ret = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &size, &deviceId);
+
// outputDevice is set to 0 if there is no audio device available
// or if the default device is set to an encoded format
if (ret != noErr || !deviceId)
{
CLog::Log(LOGERROR, "CCoreAudioHardware::GetDefaultOutputDevice:"
" Unable to identify default output device. Error = %s", GetError(ret).c_str());
- return 0;
+ // if there was no error and no deviceId was returned
+ // return the last known default device
+ if (ret == noErr && !deviceId)
+ return lastDeviceId;
+ else
+ return 0;
}
+
+ lastDeviceId = deviceId;
return deviceId;
}
if (ret != noErr)
return;
- const char *cstr = CFStringGetCStringPtr(theDeviceName, kCFStringEncodingUTF8);
- if (cstr)
- name = cstr;
+ DarwinCFStringRefToString(theDeviceName, name);
+
CFRelease(theDeviceName);
}
}
{
CLog::Log(LOGDEBUG, "CSoftAE::Suspend - Suspending AE processing");
m_isSuspended = true;
-
CSingleLock streamLock(m_streamLock);
for (StreamList::iterator itt = m_playingStreams.begin(); itt != m_playingStreams.end(); ++itt)
restart = true;
}
-#if !defined(TARGET_ANDROID)
/* Handle idle or forced suspend */
ProcessSuspend();
-#endif
/* if we are told to restart */
if (m_reOpen || restart || !m_sink)
InternalOpenSink();
m_isSuspended = false; // exit Suspend state
}
+#if defined(TARGET_ANDROID)
+ else if (m_playingStreams.empty()
+ && m_playing_sounds.empty()
+ && !g_advancedSettings.m_streamSilence)
+ {
+ // if we have nothing to do, take a dirt nap.
+ // we do not have to take a lock just to check empty.
+ // this keeps AE from sucking CPU if nothing is going on.
+ m_wake.WaitMSec(SOFTAE_IDLE_WAIT_MSEC);
+ }
+#endif
}
}
inline void CSoftAE::ProcessSuspend()
{
bool sinkIsSuspended = false;
+ unsigned int curSystemClock = 0;
- if (m_playingStreams.empty() && m_playing_sounds.empty() &&
- !m_softSuspend && !g_advancedSettings.m_streamSilence)
+#if defined(TARGET_WINDOWS)
+ if (!m_softSuspend && m_playingStreams.empty() && m_playing_sounds.empty() &&
+ !g_advancedSettings.m_streamSilence)
{
m_softSuspend = true;
m_softSuspendTimer = XbmcThreads::SystemClockMillis() + 10000; //10.0 second delay for softSuspend
+ Sleep(10);
}
- unsigned int curSystemClock = XbmcThreads::SystemClockMillis();
+ if (m_softSuspend)
+ curSystemClock = XbmcThreads::SystemClockMillis();
+#endif
/* idle while in Suspend() state until Resume() called */
/* idle if nothing to play and user hasn't enabled */
return true;
}
-void CAESinkAUDIOTRACK::SetVolume(float volume)
+void CAESinkAUDIOTRACK::SetVolume(float scale)
{
- m_volume = volume;
+ // Android uses fixed steps, reverse scale back to percent
+ float gain = CAEUtil::ScaleToGain(scale);
+ m_volume = CAEUtil::GainToPercent(gain);
m_volume_changed = true;
}
jmethodID jmRelease = jenv->GetMethodID(jcAudioTrack, "release", "()V");
jmethodID jmWrite = jenv->GetMethodID(jcAudioTrack, "write", "([BII)I");
jmethodID jmPlayState = jenv->GetMethodID(jcAudioTrack, "getPlayState", "()I");
- jmethodID jmSetStereoVolume = jenv->GetMethodID(jcAudioTrack, "setStereoVolume", "(FF)I");
jmethodID jmPlayHeadPosition = jenv->GetMethodID(jcAudioTrack, "getPlaybackHeadPosition", "()I");
jmethodID jmGetMinBufferSize = jenv->GetStaticMethodID(jcAudioTrack, "getMinBufferSize", "(III)I");
{
// check of volume changes and make them,
// do it here to keep jni calls local to this thread.
- jfloat jvolume = m_volume;
- jenv->CallIntMethod(joAudioTrack, jmSetStereoVolume, jvolume, jvolume);
+ CXBMCApp::SetSystemVolume(jenv, m_volume);
m_volume_changed = false;
}
if (m_draining)
virtual unsigned int AddPackets (uint8_t *data, unsigned int frames, bool hasAudio);
virtual void Drain ();
virtual bool HasVolume ();
- virtual void SetVolume (float volume);
+ virtual void SetVolume (float scale);
static void EnumerateDevicesEx(AEDeviceInfoList &list);
private:
return (value - 1)*db_range;
}
+ /*! \brief convert a dB gain to volume percentage (as a proportion)
+ We assume a dB range of 60dB, i.e. assume that 0% volume corresponds
+ to a reduction of 60dB.
+ \param the corresponding gain in dB from -60dB .. 0dB.
+ \return value the volume from 0..1
+ \sa ScaleToGain
+ */
+ static inline const float GainToPercent(const float gain)
+ {
+ static const float db_range = 60.0f;
+ return 1+(gain/db_range);
+ }
+
/*! \brief convert a dB gain to a scale factor for audio manipulation
Inverts gain = 20 log_10(scale)
\param dB the gain in decibels.
return pow(10.0f, dB/20);
}
+ /*! \brief convert a scale factor to dB gain for audio manipulation
+ Inverts GainToScale result
+ \param the scale factor (equivalent to a voltage multiplier).
+ \return dB the gain in decibels.
+ \sa GainToScale
+ */
+ static inline const float ScaleToGain(const float scale)
+ {
+ return 20*log10(scale);
+ }
+
#ifdef __SSE__
static void SSEMulArray (float *data, const float mul, uint32_t count);
static void SSEMulAddArray (float *data, float *add, const float mul, uint32_t count);
virtual void SeekPercentage(float fPercent = 0){}
virtual float GetPercentage(){ return 0;}
virtual float GetCachePercentage(){ return 0;}
+ virtual void SetMute(bool bOnOff){}
virtual void SetVolume(float volume){}
+ virtual bool ControlsVolume(){ return false;}
virtual void SetDynamicRangeCompression(long drc){}
virtual void GetAudioInfo( CStdString& strAudioInfo) = 0;
virtual void GetVideoInfo( CStdString& strVideoInfo) = 0;
for (size_t j = (int)RES_DESKTOP; j < g_settings.m_ResInfo.size(); j++)
{
- if (g_settings.m_ResInfo[j].iWidth == g_settings.m_ResInfo[m_resolution].iWidth
- && g_settings.m_ResInfo[j].iHeight == g_settings.m_ResInfo[m_resolution].iHeight
- && g_settings.m_ResInfo[j].iScreen == g_settings.m_ResInfo[m_resolution].iScreen)
+ if (g_settings.m_ResInfo[j].iScreenWidth == g_settings.m_ResInfo[m_resolution].iScreenWidth
+ && g_settings.m_ResInfo[j].iScreenHeight == g_settings.m_ResInfo[m_resolution].iScreenHeight
+ && g_settings.m_ResInfo[j].iScreen == g_settings.m_ResInfo[m_resolution].iScreen)
{
if (g_settings.m_ResInfo[j].fRefreshRate <= override.refreshmax
&& g_settings.m_ResInfo[j].fRefreshRate >= override.refreshmin)
for (size_t i = (int)RES_DESKTOP; i < g_settings.m_ResInfo.size(); i++)
{
if (MathUtils::round_int(g_settings.m_ResInfo[i].fRefreshRate) == 60
- && g_settings.m_ResInfo[i].iWidth == g_settings.m_ResInfo[m_resolution].iWidth
- && g_settings.m_ResInfo[i].iHeight == g_settings.m_ResInfo[m_resolution].iHeight
- && g_settings.m_ResInfo[i].iScreen == g_settings.m_ResInfo[m_resolution].iScreen)
+ && g_settings.m_ResInfo[i].iScreenWidth == g_settings.m_ResInfo[m_resolution].iScreenWidth
+ && g_settings.m_ResInfo[i].iScreenHeight == g_settings.m_ResInfo[m_resolution].iScreenHeight
+ && g_settings.m_ResInfo[i].iScreen == g_settings.m_ResInfo[m_resolution].iScreen)
{
if (fabs(g_settings.m_ResInfo[i].fRefreshRate - 60.0) < fabs(g_settings.m_ResInfo[m_resolution].fRefreshRate - 60.0))
m_resolution = (RESOLUTION)i;
CLog::Log(LOGDEBUG, "60 hertz refreshrate not available, choosing highest");
for (size_t i = (int)RES_DESKTOP; i < g_settings.m_ResInfo.size(); i++)
{
- if (g_settings.m_ResInfo[i].fRefreshRate > g_settings.m_ResInfo[m_resolution].fRefreshRate
- && g_settings.m_ResInfo[i].iWidth == g_settings.m_ResInfo[m_resolution].iWidth
- && g_settings.m_ResInfo[i].iHeight == g_settings.m_ResInfo[m_resolution].iHeight
- && g_settings.m_ResInfo[i].iScreen == g_settings.m_ResInfo[m_resolution].iScreen)
+ if (g_settings.m_ResInfo[i].fRefreshRate > g_settings.m_ResInfo[m_resolution].fRefreshRate
+ && g_settings.m_ResInfo[i].iScreenWidth == g_settings.m_ResInfo[m_resolution].iScreenWidth
+ && g_settings.m_ResInfo[i].iScreenHeight == g_settings.m_ResInfo[m_resolution].iScreenHeight
+ && g_settings.m_ResInfo[i].iScreen == g_settings.m_ResInfo[m_resolution].iScreen)
{
m_resolution = (RESOLUTION)i;
}
{
RESOLUTION_INFO &curr = g_settings.m_ResInfo[current];
- int iWidth = curr.iWidth;
- int iHeight = curr.iHeight;
+ int iScreenWidth = curr.iScreenWidth;
+ int iScreenHeight = curr.iScreenHeight;
float fRefreshRate = fps;
/*
* For 3D modes the following is assumed :
*
- * fps is fps * 2 : 25 fps -> 50 fps
- *
* side-by-side :
*
* width is width / 2 : 1920 -> 960
*
*/
+ // work out current non-3D resolution (in case we are currently in 3D mode)
+ if (curr.dwFlags & D3DPRESENTFLAG_MODE3DSBS)
+ {
+ iScreenWidth *= 2;
+ }
+ else if (curr.dwFlags & D3DPRESENTFLAG_MODE3DTB)
+ {
+ iScreenHeight *= 2;
+ }
+ // work out resolution if we switch to 3D mode
if(m_iFlags & CONF_FLAGS_FORMAT_SBS)
{
- iWidth /= 2;
- fRefreshRate *= 2;
+ iScreenWidth /= 2;
}
else if(m_iFlags & CONF_FLAGS_FORMAT_TB)
{
- iHeight /= 2;
- fRefreshRate *= 2;
+ iScreenHeight /= 2;
}
float last_diff = fRefreshRate;
//discard resolutions that are not the same width and height
//or have a too low refreshrate
- if (info.iWidth != iWidth
- || info.iHeight != iHeight
+ if (info.iScreenWidth != iScreenWidth
+ || info.iScreenHeight != iScreenHeight
|| info.iScreen != curr.iScreen
|| info.fRefreshRate < (fRefreshRate * multiplier / 1.001) - 0.001)
continue;
//***************************************************************************************
void CBaseRenderer::CalculateFrameAspectRatio(unsigned int desired_width, unsigned int desired_height)
{
- if(m_iFlags & CONF_FLAGS_FORMAT_SBS)
- desired_width /= 2;
- else if(m_iFlags & CONF_FLAGS_FORMAT_TB)
- desired_height /= 2;
-
m_sourceFrameRatio = (float)desired_width / desired_height;
// Check whether mplayer has decided that the size of the video file should be changed
if (viewMode < VIEW_MODE_NORMAL || viewMode > VIEW_MODE_CUSTOM)
viewMode = VIEW_MODE_NORMAL;
+ if (m_iFlags & (CONF_FLAGS_FORMAT_SBS | CONF_FLAGS_FORMAT_TB))
+ viewMode = VIEW_MODE_NORMAL;
+
g_settings.m_currentVideoSettings.m_ViewMode = viewMode;
// get our calibrated full screen resolution
return 0.0f;
}
-void CAMLPlayer::SetVolume(float volume)
+void CAMLPlayer::SetMute(bool bOnOff)
{
- CLog::Log(LOGDEBUG, "CAMLPlayer::SetVolume(%f)", volume);
+ m_audio_mute = bOnOff;
#if !defined(TARGET_ANDROID)
CSingleLock lock(m_aml_csection);
- // volume is a float percent from 0.0 to 1.0
if (m_dll->check_pid_valid(m_pid))
- m_dll->audio_set_volume(m_pid, volume);
+ {
+ if (m_audio_mute)
+ m_dll->audio_set_volume(m_pid, 0.0);
+ else
+ m_dll->audio_set_volume(m_pid, m_audio_volume);
+ }
+#endif
+}
+
+void CAMLPlayer::SetVolume(float volume)
+{
+ m_audio_volume = 0.0f;
+ if (volume > VOLUME_MINIMUM)
+ {
+ float dB = CAEUtil::PercentToGain(volume);
+ m_audio_volume = CAEUtil::GainToScale(dB);
+ }
+ if (m_audio_volume >= 0.99f)
+ m_audio_volume = 1.0f;
+
+#if !defined(TARGET_ANDROID)
+ CSingleLock lock(m_aml_csection);
+ if (!m_audio_mute && m_dll->check_pid_valid(m_pid))
+ m_dll->audio_set_volume(m_pid, m_audio_volume);
#endif
}
m_dll->player_timesearch(m_pid, (float)seek_ms/1000.0);
WaitForSearchOK(5000);
WaitForPlaying(5000);
+ // restore system volume setting.
+ SetVolume(m_audio_volume);
}
}
// get our initial status.
GetStatus();
+ // restore mute setting.
+ SetMute(g_settings.m_bMute);
+
// restore system volume setting.
SetVolume(g_settings.m_fVolumeLevel);
bool CAMLPlayer::WaitForPlaying(int timeout_ms)
{
+ // force the volume off in case we are starting muted
+ m_audio_mute = true;
while (!m_bAbortRequest && (timeout_ms > 0))
{
+#if !defined(TARGET_ANDROID)
+ // anoying that we have to hammer audio_set_volume
+ // but have to catch it before any audio comes out.
+ m_dll->audio_set_volume(m_pid, 0.0);
+#endif
player_status pstatus = (player_status)GetPlayerSerializedState();
if (m_log_level > 5)
CLog::Log(LOGDEBUG, "CAMLPlayer::WaitForPlaying: %s", m_dll->player_status2str(pstatus));
virtual bool SeekScene(bool bPlus = true);
virtual void SeekPercentage(float fPercent = 0.0f);
virtual float GetPercentage();
+ virtual void SetMute(bool bOnOff);
virtual void SetVolume(float volume);
+ virtual bool ControlsVolume() {return true;}
virtual void SetDynamicRangeCompression(long drc) {}
virtual void GetAudioInfo(CStdString &strAudioInfo);
virtual void GetVideoInfo(CStdString &strVideoInfo);
int m_audio_delay;
bool m_audio_passthrough_ac3;
bool m_audio_passthrough_dts;
+ bool m_audio_mute;
+ float m_audio_volume;
int m_video_index;
int m_video_count;
virtual void SetDropState(bool bDrop) = 0;
/*
+ * will be called by video player indicating the playback speed. see DVD_PLAYSPEED_NORMAL,
+ * DVD_PLAYSPEED_PAUSE and friends.
+ */
+ virtual void SetSpeed(int iSpeed) {};
+
+ /*
* returns the number of demuxer bytes in any internal buffers
*/
virtual int GetDataSize(void)
}
m_use_cvBufferRef = true;
+#if 0
//TODO fix after Frodo if (g_Windowing.GetRenderVendor().Find("Intel") > -1)
{
m_dllSwScale = new DllSwScale;
m_use_cvBufferRef = false;
}
+#endif
// setup the decoder configuration dict
CFMutableDictionaryRef decoderConfiguration = CFDictionaryCreateMutable(
m_speed = static_cast<CDVDMsgInt*>(pMsg)->m_value;
if(m_speed == DVD_PLAYSPEED_PAUSE)
m_iNrOfPicturesNotToSkip = 0;
+ if (m_pVideoCodec)
+ m_pVideoCodec->SetSpeed(m_speed);
}
else if (pMsg->IsType(CDVDMsg::PLAYER_STARTED))
{
* Only check for edit decision lists if the movie is on the local hard drive, or accessed over a
* network share.
*/
- if ((URIUtils::IsHD(strMovie) || URIUtils::IsSmb(strMovie)) &&
+ if ((URIUtils::IsHD(strMovie) ||
+ URIUtils::IsSmb(strMovie) ||
+ URIUtils::IsNfs(strMovie) ||
+ URIUtils::IsAfp(strMovie)) &&
!URIUtils::IsPVRRecording(strMovie) &&
!URIUtils::IsInternetStream(strMovie))
{
#include "guilib/LocalizeStrings.h"
#include "cores/AudioEngine/Utils/AEConvert.h"
-#ifndef VOLUME_MINIMUM
-#define VOLUME_MINIMUM -6000 // -60dB
-#endif
-
using namespace std;
#define OMX_MAX_CHANNELS 10
static const uint16_t DTSFSCod [] = {0, 8000, 16000, 32000, 0, 0, 11025, 22050, 44100, 0, 0, 12000, 24000, 48000, 0, 0};
+// 7.1 downmixing coefficients
+const float downmixing_coefficients_8[OMX_AUDIO_MAXCHANNELS] = {
+ // L R
+ /* L */ 1, 0,
+ /* R */ 0, 1,
+ /* C */ 0.7071, 0.7071,
+ /* LFE */ 0.7071, 0.7071,
+ /* Ls */ 0.7071, 0,
+ /* Rs */ 0, 0.7071,
+ /* Lr */ 0.7071, 0,
+ /* Rr */ 0, 0.7071
+};
+
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
m_drc = 0;
- m_CurrentVolume = g_settings.m_fVolumeLevel;
-
memset(m_input_channels, 0x0, sizeof(m_input_channels));
memset(m_output_channels, 0x0, sizeof(m_output_channels));
memset(&m_wave_header, 0x0, sizeof(m_wave_header));
m_first_frame = true;
m_last_pts = DVD_NOPTS_VALUE;
- SetCurrentVolume(m_CurrentVolume);
-
CLog::Log(LOGDEBUG, "COMXAudio::Initialize Ouput bps %d samplerate %d channels %d buffer size %d bytes per second %d",
(int)m_pcm_output.nBitPerSample, (int)m_pcm_output.nSamplingRate, (int)m_pcm_output.nChannels, m_BufferLen, m_BytesPerSec);
CLog::Log(LOGDEBUG, "COMXAudio::Initialize Input bps %d samplerate %d channels %d buffer size %d bytes per second %d",
CSingleLock lock (m_critSection);
if(!m_Initialized || m_Passthrough)
- return -1;
-
+ return false;
+ double gain = pow(10, (g_advancedSettings.m_ac3Gain - 12.0f) / 20.0);
m_CurrentVolume = fVolume;
- OMX_AUDIO_CONFIG_VOLUMETYPE volume;
- OMX_INIT_STRUCTURE(volume);
- volume.nPortIndex = m_omx_render->GetInputPort();
+ if (m_format.m_channelLayout.Count() > 2)
+ {
+ double r = fVolume;
+ const float* coeff = downmixing_coefficients_8;
+ int input_channels = 0;
+
+ // normally we normalalise the levels, can be skipped (boosted) at risk of distortion
+ if(!g_guiSettings.GetBool("audiooutput.normalizelevels"))
+ {
+ double sum_L = 0;
+ double sum_R = 0;
+
+ for(size_t i = 0; i < OMX_AUDIO_MAXCHANNELS; ++i)
+ {
+ if (m_input_channels[i] == OMX_AUDIO_ChannelMax)
+ break;
+ if(i & 1)
+ sum_R += coeff[i];
+ else
+ sum_L += coeff[i];
+ }
+
+ r /= max(sum_L, sum_R);
+ }
+
+ // the analogue volume is too quiet for some. Allow use of an advancedsetting to boost this (at risk of distortion)
+ r *= gain;
- volume.bLinear = OMX_TRUE;
- float hardwareVolume = std::max(VOLUME_MINIMUM, std::min(VOLUME_MAXIMUM, fVolume)) * 100.0f;
- volume.sVolume.nValue = (int)hardwareVolume;
+ OMX_CONFIG_BRCMAUDIODOWNMIXCOEFFICIENTS mix;
+ OMX_INIT_STRUCTURE(mix);
+ mix.nPortIndex = m_omx_mixer.GetInputPort();
- m_omx_render->SetConfig(OMX_IndexConfigAudioVolume, &volume);
+ assert(sizeof(mix.coeff)/sizeof(mix.coeff[0]) == 16);
+ for(size_t i = 0; i < 16; ++i)
+ mix.coeff[i] = static_cast<unsigned int>(0x10000 * (coeff[i] * r));
+
+ OMX_ERRORTYPE omx_err =
+ m_omx_mixer.SetConfig(OMX_IndexConfigBrcmAudioDownmixCoefficients, &mix);
+
+ if(omx_err != OMX_ErrorNone)
+ {
+ CLog::Log(LOGERROR, "%s::%s - error setting OMX_IndexConfigBrcmAudioDownmixCoefficients, error 0x%08x\n",
+ CLASSNAME, __func__, omx_err);
+ return false;
+ }
+ }
+ else
+ {
+ OMX_AUDIO_CONFIG_VOLUMETYPE volume;
+ OMX_INIT_STRUCTURE(volume);
+ volume.nPortIndex = m_omx_render->GetInputPort();
+
+ volume.bLinear = OMX_TRUE;
+ float hardwareVolume = fVolume * gain * 100.0f;
+ volume.sVolume.nValue = (int)(hardwareVolume + 0.5f);
+
+ OMX_ERRORTYPE omx_err =
+ m_omx_render->SetConfig(OMX_IndexConfigAudioVolume, &volume);
+ if(omx_err != OMX_ErrorNone)
+ {
+ CLog::Log(LOGERROR, "%s::%s - error setting OMX_IndexConfigAudioVolume, error 0x%08x\n",
+ CLASSNAME, __func__, omx_err);
+ return false;
+ }
+ }
return true;
}
if(m_av_clock->AudioStart())
{
omx_buffer->nFlags = OMX_BUFFERFLAG_STARTTIME;
+ if(pts == DVD_NOPTS_VALUE)
+ omx_buffer->nFlags |= OMX_BUFFERFLAG_TIME_UNKNOWN;
m_last_pts = pts;
}
}
- if ((m_pcm_input.nChannels > m_pcm_output.nChannels) &&g_guiSettings.GetBool("audiooutput.normalizelevels"))
- {
- OMX_AUDIO_CONFIG_VOLUMETYPE volume;
- OMX_INIT_STRUCTURE(volume);
- volume.nPortIndex = m_omx_mixer.GetInputPort();
- volume.bLinear = OMX_FALSE;
- volume.sVolume.nValue = (int)(g_advancedSettings.m_ac3Gain*100.0f+0.5f);
- m_omx_mixer.SetConfig(OMX_IndexConfigAudioVolume, &volume);
- }
-
memcpy(m_pcm_input.eChannelMapping, m_input_channels, sizeof(m_input_channels));
m_pcm_input.nSamplingRate = m_format.m_sampleRate;
m_UpdateApplication = 0;
m_offset_pts = 0;
m_current_volume = 0;
+ m_current_mute = false;
m_change_volume = true;
m_item = file;
if(m_change_volume)
{
- m_player_audio.SetCurrentVolume(m_current_volume);
+ m_player_audio.SetCurrentVolume(m_current_mute ? VOLUME_MINIMUM : m_current_volume);
m_change_volume = false;
}
m_player_video.SendMessage(new CDVDMsg(CDVDMsg::GENERAL_RESET));
unsigned flags = 0;
- if(m_filename.find("3DSBS") != string::npos)
+ if(m_filename.find("3DSBS") != string::npos || m_filename.find("HSBS") != string::npos)
flags = CONF_FLAGS_FORMAT_SBS;
+ else if(m_filename.find("3DTAB") != string::npos || m_filename.find("HTAB") != string::npos)
+ flags = CONF_FLAGS_FORMAT_TB;
m_player_video.SetFlags(flags);
/* store information about stream */
g_renderManager.GetVideoRect(SrcRect, DestRect);
}
+void COMXPlayer::SetMute(bool bOnOff)
+{
+ m_current_mute = bOnOff;
+ m_change_volume = true;
+}
+
void COMXPlayer::SetVolume(float fVolume)
{
m_current_volume = fVolume;
virtual float GetPercentage();
virtual float GetCachePercentage();
+ virtual void SetMute(bool bOnOff);
virtual void SetVolume(float fVolume);
+ virtual bool ControlsVolume() {return true;}
virtual void SetDynamicRangeCompression(long drc) {}
virtual void GetAudioInfo(CStdString &strAudioInfo);
virtual void GetVideoInfo(CStdString &strVideoInfo);
CEvent m_ready;
float m_current_volume;
+ bool m_current_mute;
bool m_change_volume;
CDVDOverlayContainer m_overlayContainer;
ECacheState m_caching;
m_messageQueue.SetMaxTimeSize(8.0);
RESOLUTION res = g_graphicsContext.GetVideoResolution();
- m_video_width = g_settings.m_ResInfo[res].iWidth;
- m_video_height = g_settings.m_ResInfo[res].iHeight;
+ m_video_width = g_settings.m_ResInfo[res].iScreenWidth;
+ m_video_height = g_settings.m_ResInfo[res].iScreenHeight;
m_dst_rect.SetRect(0, 0, 0, 0);
flags |= CONF_FLAGS_FORMAT_SBS;
}
}
-
- CLog::Log(LOGDEBUG,"%s - change configuration. %dx%d. framerate: %4.2f. format: BYPASS",
- __FUNCTION__, m_width, m_height, m_fps);
+ else if(m_flags & CONF_FLAGS_FORMAT_TB)
+ {
+ if(g_Windowing.Support3D(m_video_width, m_video_height, D3DPRESENTFLAG_MODE3DTB))
+ {
+ CLog::Log(LOGNOTICE, "3DTB movie found");
+ flags |= CONF_FLAGS_FORMAT_TB;
+ }
+ }
unsigned int iDisplayWidth = m_hints.width;
unsigned int iDisplayHeight = m_hints.height;
+
/* use forced aspect if any */
if( m_fForcedAspectRatio != 0.0f )
iDisplayWidth = (int) (iDisplayHeight * m_fForcedAspectRatio);
+ CLog::Log(LOGDEBUG,"%s - change configuration. %dx%d. framerate: %4.2f. %dx%x format: BYPASS",
+ __FUNCTION__, m_width, m_height, m_fps, iDisplayWidth, iDisplayHeight);
+
if(!g_renderManager.Configure(m_hints.width, m_hints.height,
iDisplayWidth, iDisplayHeight, m_fps, flags, format, 0,
m_hints.orientation))
CRect gui, display, dst_rect;
RESOLUTION res = g_graphicsContext.GetVideoResolution();
gui.SetRect(0, 0, g_settings.m_ResInfo[res].iWidth, g_settings.m_ResInfo[res].iHeight);
- display.SetRect(0, 0, g_settings.m_ResInfo[res].iWidth, g_settings.m_ResInfo[res].iHeight);
+ display.SetRect(0, 0, g_settings.m_ResInfo[res].iScreenWidth, g_settings.m_ResInfo[res].iScreenHeight);
dst_rect = m_dst_rect;
if (gui != display)
{
float xscale = display.Width() / gui.Width();
float yscale = display.Height() / gui.Height();
+ // video is displayed in absolute coordinates (bypassing half width or height GUI mode)
+ if (m_flags & CONF_FLAGS_FORMAT_SBS) xscale *= 2.0f;
+ if (m_flags & CONF_FLAGS_FORMAT_TB) yscale *= 2.0f;
dst_rect.x1 *= xscale;
dst_rect.x2 *= xscale;
dst_rect.y1 *= yscale;
dst_rect.y2 *= yscale;
}
-
- if(!(m_flags & CONF_FLAGS_FORMAT_SBS) && !(m_flags & CONF_FLAGS_FORMAT_TB))
- m_omxVideo.SetVideoRect(SrcRect, m_dst_rect);
+ m_omxVideo.SetVideoRect(SrcRect, dst_rect);
}
void OMXPlayerVideo::RenderUpdateCallBack(const void *ctx, const CRect &SrcRect, const CRect &DestRect)
}
}
+ // broadcom omx entension:
+ // When enabled, the timestamp fifo mode will change the way incoming timestamps are associated with output images.
+ // In this mode the incoming timestamps get used without re-ordering on output images.
+ if(hints.ptsinvalid)
+ {
+ OMX_CONFIG_BOOLEANTYPE timeStampMode;
+ OMX_INIT_STRUCTURE(timeStampMode);
+ timeStampMode.bEnabled = OMX_TRUE;
+
+ omx_err = m_omx_decoder.SetParameter((OMX_INDEXTYPE)OMX_IndexParamBrcmVideoTimestampFifo, &timeStampMode);
+ if (omx_err != OMX_ErrorNone)
+ {
+ CLog::Log(LOGERROR, "COMXVideo::Open OMX_IndexParamBrcmVideoTimestampFifo error (0%08x)\n", omx_err);
+ return false;
+ }
+ }
+
if(NaluFormatStartCodes(hints.codec, m_extradata, m_extrasize))
{
OMX_NALSTREAMFORMATTYPE nalStreamFormat;
m_deinterlace, m_hdmi_clock_sync);
m_first_frame = true;
+ // start from assuming all recent frames had valid pts
+ m_history_valid_pts = ~0;
+
return true;
}
return m_omx_decoder.GetInputBufferSize();
}
+static unsigned count_bits(int32_t value)
+{
+ unsigned bits = 0;
+ for(;value;++bits)
+ value &= value - 1;
+ return bits;
+}
+
int COMXVideo::Decode(uint8_t *pData, int iSize, double dts, double pts)
{
OMX_ERRORTYPE omx_err;
omx_buffer->nFlags = 0;
omx_buffer->nOffset = 0;
-
- uint64_t val = (uint64_t)(pts == DVD_NOPTS_VALUE) ? 0 : pts;
+ // some packed bitstream AVI files set almost all pts values to DVD_NOPTS_VALUE, but have a scattering of real pts values.
+ // the valid pts values match the dts values.
+ // if a stream has had more than 4 valid pts values in the last 16, the use UNKNOWN, otherwise use dts
+ m_history_valid_pts = (m_history_valid_pts << 1) | (pts != DVD_NOPTS_VALUE);
+ if(pts == DVD_NOPTS_VALUE && count_bits(m_history_valid_pts & 0xffff) < 4)
+ pts = dts;
if(m_av_clock->VideoStart())
{
- // only send dts on first frame to get nerly correct starttime
+ // only send dts on first frame to get nearly correct starttime
if(pts == DVD_NOPTS_VALUE)
pts = dts;
- val = (uint64_t)(pts == DVD_NOPTS_VALUE) ? 0 : pts;
-
+ if(pts == DVD_NOPTS_VALUE)
+ omx_buffer->nFlags |= OMX_BUFFERFLAG_TIME_UNKNOWN;
omx_buffer->nFlags = OMX_BUFFERFLAG_STARTTIME;
- CLog::Log(LOGDEBUG, "OMXVideo::Decode VDec : setStartTime %f\n", (float)val / DVD_TIME_BASE);
+ CLog::Log(LOGDEBUG, "OMXVideo::Decode VDec : setStartTime %f\n", (pts == DVD_NOPTS_VALUE ? 0.0 : pts) / DVD_TIME_BASE);
m_av_clock->VideoStart(false);
- omx_buffer->nTimeStamp = ToOMXTime(val);
}
else
{
if(pts == DVD_NOPTS_VALUE)
omx_buffer->nFlags = OMX_BUFFERFLAG_TIME_UNKNOWN;
- omx_buffer->nTimeStamp = ToOMXTime(val);
}
-
+ omx_buffer->nTimeStamp = ToOMXTime((uint64_t)(pts == DVD_NOPTS_VALUE) ? 0 : pts);
omx_buffer->nFilledLen = (demuxer_bytes > omx_buffer->nAllocLen) ? omx_buffer->nAllocLen : demuxer_bytes;
memcpy(omx_buffer->pBuffer, demuxer_content, omx_buffer->nFilledLen);
return;
OMX_CONFIG_DISPLAYREGIONTYPE configDisplay;
- OMX_INIT_STRUCTURE(configDisplay);
- configDisplay.nPortIndex = m_omx_render.GetInputPort();
- RESOLUTION res = g_graphicsContext.GetVideoResolution();
- // DestRect is in GUI coordinates, rather than display coordinates, so we have to scale
- float xscale = (float)g_settings.m_ResInfo[res].iScreenWidth / (float)g_settings.m_ResInfo[res].iWidth;
- float yscale = (float)g_settings.m_ResInfo[res].iScreenHeight / (float)g_settings.m_ResInfo[res].iHeight;
float sx1 = SrcRect.x1, sy1 = SrcRect.y1, sx2 = SrcRect.x2, sy2 = SrcRect.y2;
- float dx1 = DestRect.x1*xscale, dy1 = DestRect.y1*yscale, dx2 = DestRect.x2*xscale, dy2 = DestRect.y2*yscale;
+ float dx1 = DestRect.x1, dy1 = DestRect.y1, dx2 = DestRect.x2, dy2 = DestRect.y2;
float sw = SrcRect.Width() / DestRect.Width();
float sh = SrcRect.Height() / DestRect.Height();
dy1 -= dy1;
}
+ OMX_INIT_STRUCTURE(configDisplay);
+ configDisplay.nPortIndex = m_omx_render.GetInputPort();
configDisplay.fullscreen = OMX_FALSE;
configDisplay.noaspect = OMX_TRUE;
bool m_deinterlace;
bool m_hdmi_clock_sync;
bool m_first_frame;
+ uint32_t m_history_valid_pts;
bool NaluFormatStartCodes(enum CodecID codec, uint8_t *in_extradata, int in_extrasize);
};
CGUIDialogContextMenu *pMenu = (CGUIDialogContextMenu *)g_windowManager.GetWindow(WINDOW_DIALOG_CONTEXT_MENU);
if (pMenu)
{
+ if (pMenu->IsDialogRunning())
+ return -1;
+
pMenu->m_buttons = choices;
pMenu->Initialize();
pMenu->SetInitialVisibility();
{
/* first program starts before current view */
int startBlock = blockOffset - 1;
- while (m_gridIndex[channel][startBlock].item == item)
+ while (startBlock >= 0 && m_gridIndex[channel][startBlock].item == item)
startBlock--;
block = startBlock + 1;
#include "utils/URIUtils.h"
#include "FileItem.h"
#include "test/TestUtils.h"
+#include "utils/StringUtils.h"
#include <errno.h>
file->Close();
XBMC_DELETETEMPFILE(file);
}
+
+TEST(TestRarFile, StoredRAR)
+{
+ XFILE::CFile file;
+ char buf[20];
+ memset(&buf, 0, sizeof(buf));
+ CStdString reffile, strrarpath, strpathinrar;
+ CFileItemList itemlist, itemlistemptydir;
+ struct __stat64 stat_buffer;
+
+ reffile = XBMC_REF_FILE_PATH("xbmc/filesystem/test/refRARstored.rar");
+ URIUtils::CreateArchivePath(strrarpath, "rar", reffile, "");
+ ASSERT_TRUE(XFILE::CDirectory::GetDirectory(strrarpath, itemlist));
+ itemlist.Sort(SORT_METHOD_FULLPATH, SortOrderAscending);
+
+ /* /reffile.txt */
+ /*
+ * NOTE: Use of Seek gives inconsistent behavior from when seeking through
+ * an uncompressed RAR archive. See TestRarFile.Read test case.
+ */
+ strpathinrar = itemlist[1]->GetPath();
+ ASSERT_TRUE(StringUtils::EndsWith(strpathinrar, "/reffile.txt", true));
+ EXPECT_EQ(0, XFILE::CFile::Stat(strpathinrar, &stat_buffer));
+ EXPECT_TRUE((stat_buffer.st_mode & S_IFMT) | S_IFREG);
+
+ ASSERT_TRUE(file.Open(strpathinrar));
+ EXPECT_EQ(0, file.GetPosition());
+ EXPECT_EQ(1616, file.GetLength());
+ EXPECT_EQ(sizeof(buf), file.Read(buf, sizeof(buf)));
+ file.Flush();
+ EXPECT_EQ(20, file.GetPosition());
+ EXPECT_TRUE(!memcmp("About\n-----\nXBMC is ", buf, sizeof(buf) - 1));
+ EXPECT_TRUE(file.ReadString(buf, sizeof(buf)));
+ EXPECT_EQ(39, file.GetPosition());
+ EXPECT_STREQ("an award-winning fr", buf);
+ EXPECT_EQ(100, file.Seek(100));
+ EXPECT_EQ(100, file.GetPosition());
+ EXPECT_EQ(sizeof(buf), file.Read(buf, sizeof(buf)));
+ file.Flush();
+ EXPECT_EQ(120, file.GetPosition());
+ EXPECT_TRUE(!memcmp("ent hub for digital ", buf, sizeof(buf) - 1));
+ EXPECT_EQ(220, file.Seek(100, SEEK_CUR));
+ EXPECT_EQ(220, file.GetPosition());
+ EXPECT_EQ(sizeof(buf), file.Read(buf, sizeof(buf)));
+ file.Flush();
+ EXPECT_EQ(240, file.GetPosition());
+ EXPECT_TRUE(!memcmp("rs, XBMC is a non-pr", buf, sizeof(buf) - 1));
+ EXPECT_EQ(1596, file.Seek(-(int64_t)sizeof(buf), SEEK_END));
+ EXPECT_EQ(1596, file.GetPosition());
+ EXPECT_EQ(sizeof(buf), file.Read(buf, sizeof(buf)));
+ file.Flush();
+ EXPECT_EQ(1616, file.GetPosition());
+ EXPECT_TRUE(!memcmp("multimedia jukebox.\n", buf, sizeof(buf) - 1));
+ EXPECT_EQ(-1, file.Seek(100, SEEK_CUR));
+ EXPECT_EQ(1616, file.GetPosition());
+ EXPECT_EQ(0, file.Seek(0, SEEK_SET));
+ EXPECT_EQ(sizeof(buf), file.Read(buf, sizeof(buf)));
+ file.Flush();
+ EXPECT_EQ(20, file.GetPosition());
+ EXPECT_TRUE(!memcmp("About\n-----\nXBMC is ", buf, sizeof(buf) - 1));
+ EXPECT_EQ(0, file.Seek(0, SEEK_SET));
+ EXPECT_EQ(-100, file.Seek(-100, SEEK_SET));
+ file.Close();
+
+ /* /testsymlink -> testdir/reffile.txt */
+ strpathinrar = itemlist[2]->GetPath();
+ ASSERT_TRUE(StringUtils::EndsWith(strpathinrar, "/testsymlink", true));
+ EXPECT_EQ(0, XFILE::CFile::Stat(strpathinrar, &stat_buffer));
+ EXPECT_TRUE((stat_buffer.st_mode & S_IFMT) | S_IFLNK);
+
+ /*
+ * FIXME: Reading symlinks in RARs is currently broken. It takes a long time
+ * to read them and they produce erroneous results. The expected result is
+ * the target paths of the symlinks.
+ */
+ ASSERT_TRUE(file.Open(strpathinrar));
+ EXPECT_EQ(19, file.GetLength());
+ file.Close();
+
+ /* /testsymlinksubdir -> testdir/testsubdir/reffile.txt */
+ strpathinrar = itemlist[3]->GetPath();
+ ASSERT_TRUE(StringUtils::EndsWith(strpathinrar, "/testsymlinksubdir", true));
+ EXPECT_EQ(0, XFILE::CFile::Stat(strpathinrar, &stat_buffer));
+ EXPECT_TRUE((stat_buffer.st_mode & S_IFMT) | S_IFLNK);
+
+ ASSERT_TRUE(file.Open(strpathinrar));
+ EXPECT_EQ(30, file.GetLength());
+ file.Close();
+
+ /* /testdir/ */
+ strpathinrar = itemlist[0]->GetPath();
+ ASSERT_TRUE(StringUtils::EndsWith(strpathinrar, "/testdir/", true));
+ EXPECT_EQ(0, XFILE::CFile::Stat(strpathinrar, &stat_buffer));
+ EXPECT_TRUE((stat_buffer.st_mode & S_IFMT) | S_IFDIR);
+
+ itemlist.Clear();
+ ASSERT_TRUE(XFILE::CDirectory::GetDirectory(strpathinrar, itemlist));
+ itemlist.Sort(SORT_METHOD_FULLPATH, SortOrderAscending);
+
+ /* /testdir/reffile.txt */
+ strpathinrar = itemlist[1]->GetPath();
+ ASSERT_TRUE(StringUtils::EndsWith(strpathinrar, "/testdir/reffile.txt",
+ true));
+ EXPECT_EQ(0, XFILE::CFile::Stat(strpathinrar, &stat_buffer));
+ EXPECT_TRUE((stat_buffer.st_mode & S_IFMT) | S_IFREG);
+
+ ASSERT_TRUE(file.Open(strpathinrar));
+ EXPECT_EQ(0, file.GetPosition());
+ EXPECT_EQ(1616, file.GetLength());
+ EXPECT_EQ(sizeof(buf), file.Read(buf, sizeof(buf)));
+ file.Flush();
+ EXPECT_EQ(20, file.GetPosition());
+ EXPECT_TRUE(!memcmp("About\n-----\nXBMC is ", buf, sizeof(buf) - 1));
+ EXPECT_TRUE(file.ReadString(buf, sizeof(buf)));
+ EXPECT_EQ(39, file.GetPosition());
+ EXPECT_STREQ("an award-winning fr", buf);
+ EXPECT_EQ(100, file.Seek(100));
+ EXPECT_EQ(100, file.GetPosition());
+ EXPECT_EQ(sizeof(buf), file.Read(buf, sizeof(buf)));
+ file.Flush();
+ EXPECT_EQ(120, file.GetPosition());
+ EXPECT_TRUE(!memcmp("ent hub for digital ", buf, sizeof(buf) - 1));
+ EXPECT_EQ(220, file.Seek(100, SEEK_CUR));
+ EXPECT_EQ(220, file.GetPosition());
+ EXPECT_EQ(sizeof(buf), file.Read(buf, sizeof(buf)));
+ file.Flush();
+ EXPECT_EQ(240, file.GetPosition());
+ EXPECT_TRUE(!memcmp("rs, XBMC is a non-pr", buf, sizeof(buf) - 1));
+ EXPECT_EQ(1596, file.Seek(-(int64_t)sizeof(buf), SEEK_END));
+ EXPECT_EQ(1596, file.GetPosition());
+ EXPECT_EQ(sizeof(buf), file.Read(buf, sizeof(buf)));
+ file.Flush();
+ EXPECT_EQ(1616, file.GetPosition());
+ EXPECT_TRUE(!memcmp("multimedia jukebox.\n", buf, sizeof(buf) - 1));
+ EXPECT_EQ(-1, file.Seek(100, SEEK_CUR));
+ EXPECT_EQ(1616, file.GetPosition());
+ EXPECT_EQ(0, file.Seek(0, SEEK_SET));
+ EXPECT_EQ(sizeof(buf), file.Read(buf, sizeof(buf)));
+ file.Flush();
+ EXPECT_EQ(20, file.GetPosition());
+ EXPECT_TRUE(!memcmp("About\n-----\nXBMC is ", buf, sizeof(buf) - 1));
+ EXPECT_EQ(0, file.Seek(0, SEEK_SET));
+ EXPECT_EQ(-100, file.Seek(-100, SEEK_SET));
+ file.Close();
+
+ /* /testdir/testemptysubdir */
+ strpathinrar = itemlist[2]->GetPath();
+ ASSERT_TRUE(StringUtils::EndsWith(strpathinrar, "/testdir/testemptysubdir",
+ true));
+ /* TODO: Should this set the itemlist to an empty list instead? */
+ EXPECT_FALSE(XFILE::CDirectory::GetDirectory(strpathinrar, itemlistemptydir));
+ EXPECT_EQ(0, XFILE::CFile::Stat(strpathinrar, &stat_buffer));
+ EXPECT_TRUE((stat_buffer.st_mode & S_IFMT) | S_IFDIR);
+
+ /* FIXME: This directory appears a second time as a file */
+ strpathinrar = itemlist[3]->GetPath();
+ ASSERT_TRUE(StringUtils::EndsWith(strpathinrar, "/testdir/testsubdir", true));
+
+ /* /testdir/testsymlink -> testsubdir/reffile.txt */
+ strpathinrar = itemlist[4]->GetPath();
+ ASSERT_TRUE(StringUtils::EndsWith(strpathinrar, "/testdir/testsymlink",
+ true));
+ EXPECT_EQ(0, XFILE::CFile::Stat(strpathinrar, &stat_buffer));
+ EXPECT_TRUE((stat_buffer.st_mode & S_IFMT) | S_IFLNK);
+
+ ASSERT_TRUE(file.Open(strpathinrar));
+ EXPECT_EQ(22, file.GetLength());
+ file.Close();
+
+ /* /testdir/testsubdir/ */
+ strpathinrar = itemlist[0]->GetPath();
+ ASSERT_TRUE(StringUtils::EndsWith(strpathinrar, "/testdir/testsubdir/",
+ true));
+ EXPECT_EQ(0, XFILE::CFile::Stat(strpathinrar, &stat_buffer));
+ EXPECT_TRUE((stat_buffer.st_mode & S_IFMT) | S_IFDIR);
+
+ itemlist.Clear();
+ ASSERT_TRUE(XFILE::CDirectory::GetDirectory(strpathinrar, itemlist));
+ itemlist.Sort(SORT_METHOD_FULLPATH, SortOrderAscending);
+
+ /* /testdir/testsubdir/reffile.txt */
+ strpathinrar = itemlist[0]->GetPath();
+ ASSERT_TRUE(StringUtils::EndsWith(strpathinrar,
+ "/testdir/testsubdir/reffile.txt", true));
+ EXPECT_EQ(0, XFILE::CFile::Stat(strpathinrar, &stat_buffer));
+ EXPECT_TRUE((stat_buffer.st_mode & S_IFMT) | S_IFREG);
+
+ ASSERT_TRUE(file.Open(strpathinrar));
+ EXPECT_EQ(0, file.GetPosition());
+ EXPECT_EQ(1616, file.GetLength());
+ EXPECT_EQ(sizeof(buf), file.Read(buf, sizeof(buf)));
+ file.Flush();
+ EXPECT_EQ(20, file.GetPosition());
+ EXPECT_TRUE(!memcmp("About\n-----\nXBMC is ", buf, sizeof(buf) - 1));
+ EXPECT_TRUE(file.ReadString(buf, sizeof(buf)));
+ EXPECT_EQ(39, file.GetPosition());
+ EXPECT_STREQ("an award-winning fr", buf);
+ EXPECT_EQ(100, file.Seek(100));
+ EXPECT_EQ(100, file.GetPosition());
+ EXPECT_EQ(sizeof(buf), file.Read(buf, sizeof(buf)));
+ file.Flush();
+ EXPECT_EQ(120, file.GetPosition());
+ EXPECT_TRUE(!memcmp("ent hub for digital ", buf, sizeof(buf) - 1));
+ EXPECT_EQ(220, file.Seek(100, SEEK_CUR));
+ EXPECT_EQ(220, file.GetPosition());
+ EXPECT_EQ(sizeof(buf), file.Read(buf, sizeof(buf)));
+ file.Flush();
+ EXPECT_EQ(240, file.GetPosition());
+ EXPECT_TRUE(!memcmp("rs, XBMC is a non-pr", buf, sizeof(buf) - 1));
+ EXPECT_EQ(1596, file.Seek(-(int64_t)sizeof(buf), SEEK_END));
+ EXPECT_EQ(1596, file.GetPosition());
+ EXPECT_EQ(sizeof(buf), file.Read(buf, sizeof(buf)));
+ file.Flush();
+ EXPECT_EQ(1616, file.GetPosition());
+ EXPECT_TRUE(!memcmp("multimedia jukebox.\n", buf, sizeof(buf) - 1));
+ EXPECT_EQ(-1, file.Seek(100, SEEK_CUR));
+ EXPECT_EQ(1616, file.GetPosition());
+ EXPECT_EQ(0, file.Seek(0, SEEK_SET));
+ EXPECT_EQ(sizeof(buf), file.Read(buf, sizeof(buf)));
+ file.Flush();
+ EXPECT_EQ(20, file.GetPosition());
+ EXPECT_TRUE(!memcmp("About\n-----\nXBMC is ", buf, sizeof(buf) - 1));
+ EXPECT_EQ(0, file.Seek(0, SEEK_SET));
+ EXPECT_EQ(-100, file.Seek(-100, SEEK_SET));
+ file.Close();
+}
+
+TEST(TestRarFile, NormalRAR)
+{
+ XFILE::CFile file;
+ char buf[20];
+ memset(&buf, 0, sizeof(buf));
+ CStdString reffile, strrarpath, strpathinrar;
+ CFileItemList itemlist, itemlistemptydir;
+ struct __stat64 stat_buffer;
+
+ reffile = XBMC_REF_FILE_PATH("xbmc/filesystem/test/refRARnormal.rar");
+ URIUtils::CreateArchivePath(strrarpath, "rar", reffile, "");
+ ASSERT_TRUE(XFILE::CDirectory::GetDirectory(strrarpath, itemlist));
+ itemlist.Sort(SORT_METHOD_FULLPATH, SortOrderAscending);
+
+ /* /reffile.txt */
+ strpathinrar = itemlist[1]->GetPath();
+ ASSERT_TRUE(StringUtils::EndsWith(strpathinrar, "/reffile.txt", true));
+ EXPECT_EQ(0, XFILE::CFile::Stat(strpathinrar, &stat_buffer));
+ EXPECT_TRUE((stat_buffer.st_mode & S_IFMT) | S_IFREG);
+
+ ASSERT_TRUE(file.Open(strpathinrar));
+ EXPECT_EQ(0, file.GetPosition());
+ EXPECT_EQ(1616, file.GetLength());
+ EXPECT_EQ(sizeof(buf), file.Read(buf, sizeof(buf)));
+ file.Flush();
+ EXPECT_EQ(20, file.GetPosition());
+ EXPECT_TRUE(!memcmp("About\n-----\nXBMC is ", buf, sizeof(buf) - 1));
+ EXPECT_TRUE(file.ReadString(buf, sizeof(buf)));
+ EXPECT_EQ(39, file.GetPosition());
+ EXPECT_STREQ("an award-winning fr", buf);
+ EXPECT_EQ(100, file.Seek(100));
+ EXPECT_EQ(100, file.GetPosition());
+ EXPECT_EQ(sizeof(buf), file.Read(buf, sizeof(buf)));
+ file.Flush();
+ EXPECT_EQ(120, file.GetPosition());
+ EXPECT_TRUE(!memcmp("ent hub for digital ", buf, sizeof(buf) - 1));
+ EXPECT_EQ(220, file.Seek(100, SEEK_CUR));
+ EXPECT_EQ(220, file.GetPosition());
+ EXPECT_EQ(sizeof(buf), file.Read(buf, sizeof(buf)));
+ file.Flush();
+ EXPECT_EQ(240, file.GetPosition());
+ EXPECT_TRUE(!memcmp("rs, XBMC is a non-pr", buf, sizeof(buf) - 1));
+ EXPECT_EQ(1596, file.Seek(-(int64_t)sizeof(buf), SEEK_END));
+ EXPECT_EQ(1596, file.GetPosition());
+ EXPECT_EQ(sizeof(buf), file.Read(buf, sizeof(buf)));
+ file.Flush();
+ EXPECT_EQ(1616, file.GetPosition());
+ EXPECT_TRUE(!memcmp("multimedia jukebox.\n", buf, sizeof(buf) - 1));
+ EXPECT_EQ(1716, file.Seek(100, SEEK_CUR));
+ EXPECT_EQ(1716, file.GetPosition());
+ EXPECT_EQ(0, file.Seek(0, SEEK_SET));
+ EXPECT_EQ(sizeof(buf), file.Read(buf, sizeof(buf)));
+ file.Flush();
+ EXPECT_EQ(20, file.GetPosition());
+ EXPECT_TRUE(!memcmp("About\n-----\nXBMC is ", buf, sizeof(buf) - 1));
+ EXPECT_EQ(0, file.Seek(0, SEEK_SET));
+ EXPECT_EQ(-1, file.Seek(-100, SEEK_SET));
+ file.Close();
+
+ /* /testsymlink -> testdir/reffile.txt */
+ strpathinrar = itemlist[2]->GetPath();
+ ASSERT_TRUE(StringUtils::EndsWith(strpathinrar, "/testsymlink", true));
+ EXPECT_EQ(0, XFILE::CFile::Stat(strpathinrar, &stat_buffer));
+ EXPECT_TRUE((stat_buffer.st_mode & S_IFMT) | S_IFLNK);
+
+ /*
+ * FIXME: Reading symlinks in RARs is currently broken. It takes a long time
+ * to read them and they produce erroneous results. The expected result is
+ * the target paths of the symlinks.
+ */
+ ASSERT_TRUE(file.Open(strpathinrar));
+ EXPECT_EQ(19, file.GetLength());
+ file.Close();
+
+ /* /testsymlinksubdir -> testdir/testsubdir/reffile.txt */
+ strpathinrar = itemlist[3]->GetPath();
+ ASSERT_TRUE(StringUtils::EndsWith(strpathinrar, "/testsymlinksubdir", true));
+ EXPECT_EQ(0, XFILE::CFile::Stat(strpathinrar, &stat_buffer));
+ EXPECT_TRUE((stat_buffer.st_mode & S_IFMT) | S_IFLNK);
+
+ ASSERT_TRUE(file.Open(strpathinrar));
+ EXPECT_EQ(30, file.GetLength());
+ file.Close();
+
+ /* /testdir/ */
+ strpathinrar = itemlist[0]->GetPath();
+ ASSERT_TRUE(StringUtils::EndsWith(strpathinrar, "/testdir/", true));
+ EXPECT_EQ(0, XFILE::CFile::Stat(strpathinrar, &stat_buffer));
+ EXPECT_TRUE((stat_buffer.st_mode & S_IFMT) | S_IFDIR);
+
+ itemlist.Clear();
+ ASSERT_TRUE(XFILE::CDirectory::GetDirectory(strpathinrar, itemlist));
+ itemlist.Sort(SORT_METHOD_FULLPATH, SortOrderAscending);
+
+ /* /testdir/reffile.txt */
+ strpathinrar = itemlist[1]->GetPath();
+ ASSERT_TRUE(StringUtils::EndsWith(strpathinrar, "/testdir/reffile.txt",
+ true));
+ EXPECT_EQ(0, XFILE::CFile::Stat(strpathinrar, &stat_buffer));
+ EXPECT_TRUE((stat_buffer.st_mode & S_IFMT) | S_IFREG);
+
+ ASSERT_TRUE(file.Open(strpathinrar));
+ EXPECT_EQ(0, file.GetPosition());
+ EXPECT_EQ(1616, file.GetLength());
+ EXPECT_EQ(sizeof(buf), file.Read(buf, sizeof(buf)));
+ file.Flush();
+ EXPECT_EQ(20, file.GetPosition());
+ EXPECT_TRUE(!memcmp("About\n-----\nXBMC is ", buf, sizeof(buf) - 1));
+ EXPECT_TRUE(file.ReadString(buf, sizeof(buf)));
+ EXPECT_EQ(39, file.GetPosition());
+ EXPECT_STREQ("an award-winning fr", buf);
+ EXPECT_EQ(100, file.Seek(100));
+ EXPECT_EQ(100, file.GetPosition());
+ EXPECT_EQ(sizeof(buf), file.Read(buf, sizeof(buf)));
+ file.Flush();
+ EXPECT_EQ(120, file.GetPosition());
+ EXPECT_TRUE(!memcmp("ent hub for digital ", buf, sizeof(buf) - 1));
+ EXPECT_EQ(220, file.Seek(100, SEEK_CUR));
+ EXPECT_EQ(220, file.GetPosition());
+ EXPECT_EQ(sizeof(buf), file.Read(buf, sizeof(buf)));
+ file.Flush();
+ EXPECT_EQ(240, file.GetPosition());
+ EXPECT_TRUE(!memcmp("rs, XBMC is a non-pr", buf, sizeof(buf) - 1));
+ EXPECT_EQ(1596, file.Seek(-(int64_t)sizeof(buf), SEEK_END));
+ EXPECT_EQ(1596, file.GetPosition());
+ EXPECT_EQ(sizeof(buf), file.Read(buf, sizeof(buf)));
+ file.Flush();
+ EXPECT_EQ(1616, file.GetPosition());
+ EXPECT_TRUE(!memcmp("multimedia jukebox.\n", buf, sizeof(buf) - 1));
+ EXPECT_EQ(1716, file.Seek(100, SEEK_CUR));
+ EXPECT_EQ(1716, file.GetPosition());
+ EXPECT_EQ(0, file.Seek(0, SEEK_SET));
+ EXPECT_EQ(sizeof(buf), file.Read(buf, sizeof(buf)));
+ file.Flush();
+ EXPECT_EQ(20, file.GetPosition());
+ EXPECT_TRUE(!memcmp("About\n-----\nXBMC is ", buf, sizeof(buf) - 1));
+ EXPECT_EQ(0, file.Seek(0, SEEK_SET));
+ EXPECT_EQ(-1, file.Seek(-100, SEEK_SET));
+ file.Close();
+
+ /* /testdir/testemptysubdir */
+ strpathinrar = itemlist[2]->GetPath();
+ ASSERT_TRUE(StringUtils::EndsWith(strpathinrar, "/testdir/testemptysubdir",
+ true));
+ /* TODO: Should this set the itemlist to an empty list instead? */
+ EXPECT_FALSE(XFILE::CDirectory::GetDirectory(strpathinrar, itemlistemptydir));
+ EXPECT_EQ(0, XFILE::CFile::Stat(strpathinrar, &stat_buffer));
+ EXPECT_TRUE((stat_buffer.st_mode & S_IFMT) | S_IFDIR);
+
+ /* FIXME: This directory appears a second time as a file */
+ strpathinrar = itemlist[3]->GetPath();
+ ASSERT_TRUE(StringUtils::EndsWith(strpathinrar, "/testdir/testsubdir", true));
+
+ /* /testdir/testsymlink -> testsubdir/reffile.txt */
+ strpathinrar = itemlist[4]->GetPath();
+ ASSERT_TRUE(StringUtils::EndsWith(strpathinrar, "/testdir/testsymlink",
+ true));
+ EXPECT_EQ(0, XFILE::CFile::Stat(strpathinrar, &stat_buffer));
+ EXPECT_TRUE((stat_buffer.st_mode & S_IFMT) | S_IFLNK);
+
+ ASSERT_TRUE(file.Open(strpathinrar));
+ EXPECT_EQ(22, file.GetLength());
+ file.Close();
+
+ /* /testdir/testsubdir/ */
+ strpathinrar = itemlist[0]->GetPath();
+ ASSERT_TRUE(StringUtils::EndsWith(strpathinrar, "/testdir/testsubdir/",
+ true));
+ EXPECT_EQ(0, XFILE::CFile::Stat(strpathinrar, &stat_buffer));
+ EXPECT_TRUE((stat_buffer.st_mode & S_IFMT) | S_IFDIR);
+
+ itemlist.Clear();
+ ASSERT_TRUE(XFILE::CDirectory::GetDirectory(strpathinrar, itemlist));
+ itemlist.Sort(SORT_METHOD_FULLPATH, SortOrderAscending);
+
+ /* /testdir/testsubdir/reffile.txt */
+ strpathinrar = itemlist[0]->GetPath();
+ ASSERT_TRUE(StringUtils::EndsWith(strpathinrar,
+ "/testdir/testsubdir/reffile.txt", true));
+ EXPECT_EQ(0, XFILE::CFile::Stat(strpathinrar, &stat_buffer));
+ EXPECT_TRUE((stat_buffer.st_mode & S_IFMT) | S_IFREG);
+
+ ASSERT_TRUE(file.Open(strpathinrar));
+ EXPECT_EQ(0, file.GetPosition());
+ EXPECT_EQ(1616, file.GetLength());
+ EXPECT_EQ(sizeof(buf), file.Read(buf, sizeof(buf)));
+ file.Flush();
+ EXPECT_EQ(20, file.GetPosition());
+ EXPECT_TRUE(!memcmp("About\n-----\nXBMC is ", buf, sizeof(buf) - 1));
+ EXPECT_TRUE(file.ReadString(buf, sizeof(buf)));
+ EXPECT_EQ(39, file.GetPosition());
+ EXPECT_STREQ("an award-winning fr", buf);
+ EXPECT_EQ(100, file.Seek(100));
+ EXPECT_EQ(100, file.GetPosition());
+ EXPECT_EQ(sizeof(buf), file.Read(buf, sizeof(buf)));
+ file.Flush();
+ EXPECT_EQ(120, file.GetPosition());
+ EXPECT_TRUE(!memcmp("ent hub for digital ", buf, sizeof(buf) - 1));
+ EXPECT_EQ(220, file.Seek(100, SEEK_CUR));
+ EXPECT_EQ(220, file.GetPosition());
+ EXPECT_EQ(sizeof(buf), file.Read(buf, sizeof(buf)));
+ file.Flush();
+ EXPECT_EQ(240, file.GetPosition());
+ EXPECT_TRUE(!memcmp("rs, XBMC is a non-pr", buf, sizeof(buf) - 1));
+ EXPECT_EQ(1596, file.Seek(-(int64_t)sizeof(buf), SEEK_END));
+ EXPECT_EQ(1596, file.GetPosition());
+ EXPECT_EQ(sizeof(buf), file.Read(buf, sizeof(buf)));
+ file.Flush();
+ EXPECT_EQ(1616, file.GetPosition());
+ EXPECT_TRUE(!memcmp("multimedia jukebox.\n", buf, sizeof(buf) - 1));
+ EXPECT_EQ(1716, file.Seek(100, SEEK_CUR));
+ EXPECT_EQ(1716, file.GetPosition());
+ EXPECT_EQ(0, file.Seek(0, SEEK_SET));
+ EXPECT_EQ(sizeof(buf), file.Read(buf, sizeof(buf)));
+ file.Flush();
+ EXPECT_EQ(20, file.GetPosition());
+ EXPECT_TRUE(!memcmp("About\n-----\nXBMC is ", buf, sizeof(buf) - 1));
+ EXPECT_EQ(0, file.Seek(0, SEEK_SET));
+ EXPECT_EQ(-1, file.Seek(-100, SEEK_SET));
+ file.Close();
+}
#endif /*HAS_FILESYSTEM_RAR*/
XFILE::CFile file;
if (file.Open(m_texturePath.c_str(), READ_TRUNCATED))
{
+ /*
+ GetLength() will typically return values that fall into three cases:
+ 1. The real filesize. This is the typical case.
+ 2. Zero. This is the case for some http:// streams for example.
+ 3. Some value smaller than the real filesize. This is the case for an expanding file.
+
+ In order to handle all three cases, we read the file in chunks, relying on Read()
+ returning 0 at EOF. To minimize (re)allocation of the buffer, the chunksize in
+ cases 1 and 3 is set to one byte larger** than the value returned by GetLength().
+ The chunksize in case 2 is set to the larger of 64k and GetChunkSize().
+
+ We fill the buffer entirely before reallocation. Thus, reallocation never occurs in case 1
+ as the buffer is larger than the file, so we hit EOF before we hit the end of buffer.
+
+ To minimize reallocation, we double the chunksize each time up to a maxchunksize of 2MB.
+ */
unsigned int filesize = (unsigned int)file.GetLength();
- unsigned int chunksize = filesize, maxchunksize = filesize;
- if (!chunksize)
- { // no size information, so try reading chunked
- chunksize = std::max(65536U, (unsigned int)file.GetChunkSize());
- maxchunksize = std::max(chunksize, 2048*1024U); // max 2MB
- }
- unsigned int total = 0, amount = 0;
+ unsigned int chunksize = filesize ? (filesize + 1) : std::max(65536U, (unsigned int)file.GetChunkSize());
+ unsigned int maxchunksize = 2048*1024U; /* max 2MB chunksize */
+
+ unsigned int total_read = 0, free_space = 0;
while (true)
{
- if (!amount)
+ if (!free_space)
{ // (re)alloc
m_inputBuffSize += chunksize;
m_inputBuff = (unsigned char *)realloc(m_inputBuff, m_inputBuffSize);
CLog::Log(LOGERROR, "%s unable to allocate buffer of size %u", __FUNCTION__, m_inputBuffSize);
return false;
}
- amount = chunksize;
+ free_space = chunksize;
chunksize = std::min(chunksize*2, maxchunksize);
}
- unsigned int read = file.Read(m_inputBuff + total, amount);
- amount -= read;
- total += read;
- if (!read || total == filesize)
+ unsigned int read = file.Read(m_inputBuff + total_read, free_space);
+ free_space -= read;
+ total_read += read;
+ if (!read)
break;
}
- m_inputBuffSize = total;
+ m_inputBuffSize = total_read;
file.Close();
if (m_inputBuffSize == 0)
XBMCK_POWER = 0x140, // Power Macintosh power key
XBMCK_EURO = 0x141, // Some european keyboards
XBMCK_UNDO = 0x142, // Atari keyboard has Undo
+ XBMCK_SLEEP = 0x143, // Sleep button on Nyxboard remote (and others?)
// Add any other keys here
, { XBMCK_SCROLLOCK, 0, 0, XBMCVK_SCROLLLOCK, "scrolllock" }
, { XBMCK_PRINT, 0, 0, XBMCVK_PRINTSCREEN, "printscreen" }
, { XBMCK_POWER, 0, 0, XBMCVK_POWER, "power" }
+, { XBMCK_SLEEP, 0, 0, XBMCVK_SLEEP, "sleep" }
};
static int XBMCKeyTableSize = sizeof(XBMCKeyTable)/sizeof(XBMCKEYTABLE);
XBMCVK_SCROLLLOCK = 0xDC,
XBMCVK_PAUSE = 0XDD,
XBMCVK_POWER = 0XDE,
+ XBMCVK_SLEEP = 0XDF,
XBMCVK_LAST = 0xFF
} XBMCVKey;
{
case Video:
case Audio:
- CApplicationMessenger::Get().SendAction(CAction(ACTION_STOP));
+ CApplicationMessenger::Get().MediaStop(true, parameterObject["playerid"].asInteger());
return ACK;
case Picture:
bool File::write(const char* pBuffer)
{
DelayedCallGuard dg(languageHook);
- return file->Write( (void*) pBuffer, strlen( pBuffer ) + 1 ) > 0;
+ return file->Write( (void*) pBuffer, strlen( pBuffer ) ) > 0;
}
}
virtual int vc_tv_hdmi_power_on_best_3d(uint32_t width, uint32_t height, uint32_t frame_rate,
HDMI_INTERLACED_T scan_mode, EDID_MODE_MATCH_FLAG_T match_flags) = 0;
- virtual int vc_tv_hdmi_get_supported_modes(HDMI_RES_GROUP_T group, TV_SUPPORTED_MODE_T *supported_modes,
+ virtual int vc_tv_hdmi_get_supported_modes_new(HDMI_RES_GROUP_T group, TV_SUPPORTED_MODE_NEW_T *supported_modes,
uint32_t max_supported_modes, HDMI_RES_GROUP_T *preferred_group,
uint32_t *preferred_mode) = 0;
- virtual int vc_tv_hdmi_power_on_explicit(HDMI_MODE_T mode, HDMI_RES_GROUP_T group, uint32_t code) = 0;
- virtual int vc_tv_get_state(TV_GET_STATE_RESP_T *tvstate) = 0;
+ virtual int vc_tv_hdmi_power_on_explicit_new(HDMI_MODE_T mode, HDMI_RES_GROUP_T group, uint32_t code) = 0;
+ virtual int vc_tv_hdmi_set_property(const HDMI_PROPERTY_PARAM_T *property) = 0;
+ virtual int vc_tv_get_display_state(TV_DISPLAY_STATE_T *tvstate) = 0;
virtual int vc_tv_show_info(uint32_t show) = 0;
virtual int vc_gencmd(char *response, int maxlen, const char *string) = 0;
virtual void vc_tv_register_callback(TVSERVICE_CALLBACK_T callback, void *callback_data) = 0;
virtual int vc_tv_hdmi_power_on_best_3d(uint32_t width, uint32_t height, uint32_t frame_rate,
HDMI_INTERLACED_T scan_mode, EDID_MODE_MATCH_FLAG_T match_flags)
{ return ::vc_tv_hdmi_power_on_best_3d(width, height, frame_rate, scan_mode, match_flags); };
- virtual int vc_tv_hdmi_get_supported_modes(HDMI_RES_GROUP_T group, TV_SUPPORTED_MODE_T *supported_modes,
+ virtual int vc_tv_hdmi_get_supported_modes_new(HDMI_RES_GROUP_T group, TV_SUPPORTED_MODE_NEW_T *supported_modes,
uint32_t max_supported_modes, HDMI_RES_GROUP_T *preferred_group,
uint32_t *preferred_mode)
- { return ::vc_tv_hdmi_get_supported_modes(group, supported_modes, max_supported_modes, preferred_group, preferred_mode); };
- virtual int vc_tv_hdmi_power_on_explicit(HDMI_MODE_T mode, HDMI_RES_GROUP_T group, uint32_t code)
- { return ::vc_tv_hdmi_power_on_explicit(mode, group, code); };
- virtual int vc_tv_get_state(TV_GET_STATE_RESP_T *tvstate)
- { return ::vc_tv_get_state(tvstate); };
+ { return ::vc_tv_hdmi_get_supported_modes_new(group, supported_modes, max_supported_modes, preferred_group, preferred_mode); };
+ virtual int vc_tv_hdmi_power_on_explicit_new(HDMI_MODE_T mode, HDMI_RES_GROUP_T group, uint32_t code)
+ { return ::vc_tv_hdmi_power_on_explicit_new(mode, group, code); };
+ virtual int vc_tv_hdmi_set_property(const HDMI_PROPERTY_PARAM_T *property)
+ { return ::vc_tv_hdmi_set_property(property); };
+ virtual int vc_tv_get_display_state(TV_DISPLAY_STATE_T *tvstate)
+ { return ::vc_tv_get_display_state(tvstate); };
virtual int vc_tv_show_info(uint32_t show)
{ return ::vc_tv_show_info(show); };
virtual int vc_gencmd(char *response, int maxlen, const char *string)
HDMI_INTERLACED_T p4, EDID_MODE_MATCH_FLAG_T p5))
DEFINE_METHOD5(int, vc_tv_hdmi_power_on_best_3d, (uint32_t p1, uint32_t p2, uint32_t p3,
HDMI_INTERLACED_T p4, EDID_MODE_MATCH_FLAG_T p5))
- DEFINE_METHOD5(int, vc_tv_hdmi_get_supported_modes, (HDMI_RES_GROUP_T p1, TV_SUPPORTED_MODE_T *p2,
+ DEFINE_METHOD5(int, vc_tv_hdmi_get_supported_modes_new, (HDMI_RES_GROUP_T p1, TV_SUPPORTED_MODE_NEW_T *p2,
uint32_t p3, HDMI_RES_GROUP_T *p4, uint32_t *p5))
- DEFINE_METHOD3(int, vc_tv_hdmi_power_on_explicit, (HDMI_MODE_T p1, HDMI_RES_GROUP_T p2, uint32_t p3))
- DEFINE_METHOD1(int, vc_tv_get_state, (TV_GET_STATE_RESP_T *p1))
+ DEFINE_METHOD3(int, vc_tv_hdmi_power_on_explicit_new, (HDMI_MODE_T p1, HDMI_RES_GROUP_T p2, uint32_t p3))
+ DEFINE_METHOD1(int, vc_tv_hdmi_set_property, (const HDMI_PROPERTY_PARAM_T *property))
+ DEFINE_METHOD1(int, vc_tv_get_display_state, (TV_DISPLAY_STATE_T *p1))
DEFINE_METHOD1(int, vc_tv_show_info, (uint32_t p1))
DEFINE_METHOD3(int, vc_gencmd, (char *p1, int p2, const char *p3))
RESOLVE_METHOD(graphics_get_display_size)
RESOLVE_METHOD(vc_tv_hdmi_power_on_best)
RESOLVE_METHOD(vc_tv_hdmi_power_on_best_3d)
- RESOLVE_METHOD(vc_tv_hdmi_get_supported_modes)
- RESOLVE_METHOD(vc_tv_hdmi_power_on_explicit)
- RESOLVE_METHOD(vc_tv_get_state)
+ RESOLVE_METHOD(vc_tv_hdmi_get_supported_modes_new)
+ RESOLVE_METHOD(vc_tv_hdmi_power_on_explicit_new)
+ RESOLVE_METHOD(vc_tv_hdmi_set_property)
+ RESOLVE_METHOD(vc_tv_get_display_state)
RESOLVE_METHOD(vc_tv_show_info)
RESOLVE_METHOD(vc_gencmd)
RESOLVE_METHOD(vc_tv_register_callback)
[pool release];
}
-#define VK_SLEEP 0x5F
+#define VK_SLEEP 0x143
#define VK_VOLUME_MUTE 0xAD
#define VK_VOLUME_DOWN 0xAE
#define VK_VOLUME_UP 0xAF
#define VK_REWIND 0x9D
#define VK_FAST_FWD 0x9E
-- (void)MediaKeyPower
+- (void)powerKeyNotification
{
SDL_Event event;
memset(&event, 0, sizeof(event));
id3v2 = ttaFile->ID3v2Tag(false);
else if (wvFile)
ape = wvFile->APETag(false);
+ else if (mpcFile)
+ ape = mpcFile->APETag(false);
else // This is a catch all to get generic information for other files types (s3m, xm, it, mod, etc)
generic = file->tag();
for (APE::ItemListMap::ConstIterator it = itemListMap.begin(); it != itemListMap.end(); ++it)
{
if (it->first == "ARTIST") SetArtist(tag, StringListToVectorString(it->second.toStringList()));
- else if (it->first == "ALBUM ARTIST") SetAlbumArtist(tag, StringListToVectorString(it->second.toStringList()));
+ else if (it->first == "ALBUM ARTIST" || it->first == "ALBUMARTIST") SetAlbumArtist(tag, StringListToVectorString(it->second.toStringList()));
else if (it->first == "ALBUM") tag.SetAlbum(it->second.toString().to8Bit(true));
else if (it->first == "TITLE") tag.SetTitle(it->second.toString().to8Bit(true));
else if (it->first == "TRACKNUMBER" || it->first == "TRACK") tag.SetTrackNumber(it->second.toString().toInt());
- else if (it->first == "DISCNUMBER") tag.SetPartOfSet(it->second.toString().toInt());
+ else if (it->first == "DISCNUMBER" || it->first == "DISC") tag.SetPartOfSet(it->second.toString().toInt());
else if (it->first == "YEAR") tag.SetYear(it->second.toString().toInt());
else if (it->first == "GENRE") SetGenre(tag, StringListToVectorString(it->second.toStringList()));
else if (it->first == "COMMENT") tag.SetComment(it->second.toString().to8Bit(true));
bool CDNSNameCache::Lookup(const CStdString& strHostName, CStdString& strIpAddress)
{
+ if (strHostName.empty() && strIpAddress.empty())
+ return false;
+
// first see if this is already an ip address
unsigned long address = inet_addr(strHostName.c_str());
strIpAddress.Empty();
#include "guilib/GUIMessage.h"
#include "threads/SingleLock.h"
#include "utils/log.h"
+#include "osx/DarwinUtils.h"
#include <arpa/inet.h>
#include <netinet/in.h>
namespace
{
- CStdString CFStringToCStdString(const CFStringRef cfstr)
- {
- //first try the short path
- const char *p_tmp = CFStringGetCStringPtr(cfstr, kCFStringEncodingUTF8);
- if (p_tmp)
- return CStdString(p_tmp);
-
- // i'm not sure if CFStringGetMaximumSizeForEncoding
- // includes space for the termination character or not?
- // so i add 1 here to make sure..
- CFIndex buf_len = 1 + CFStringGetMaximumSizeForEncoding(
- CFStringGetLength(cfstr), kCFStringEncodingUTF8);
-
- char *buffer = new char[buf_len];
- CFStringGetCString(cfstr, buffer, buf_len, kCFStringEncodingUTF8);
- CStdString myString(buffer);
- delete[] buffer;
-
- return myString;
- }
-
//helper for getting a the txt-records list
//returns true on success, false if nothing found or error
CZeroconfBrowser::ZeroconfService::tTxtRecordMap GetTxtRecords(CFNetServiceRef serviceRef)
for(idx = 0; idx < numValues; idx++)
{
- recordMap.insert(
- std::make_pair(
- CFStringToCStdString(keys[idx]),
- CStdString((const char *)CFDataGetBytePtr(values[idx]))
- )
- );
+ std::string key;
+ if (DarwinCFStringRefToString(keys[idx], key))
+ {
+ recordMap.insert(
+ std::make_pair(
+ key,
+ CStdString((const char *)CFDataGetBytePtr(values[idx]))
+ )
+ );
+ }
}
}
CFRelease(dict);
assert(service);
//get our instance
CZeroconfBrowserOSX* p_this = reinterpret_cast<CZeroconfBrowserOSX*>(info);
+
//store the service
- ZeroconfService s(
- CFStringToCStdString(CFNetServiceGetName(service)),
- CFStringToCStdString(CFNetServiceGetType(service)),
- CFStringToCStdString(CFNetServiceGetDomain(service)));
+ std::string name, type, domain;
+ if (!DarwinCFStringRefToString(CFNetServiceGetName(service), name) ||
+ !DarwinCFStringRefToString(CFNetServiceGetType(service), type) ||
+ !DarwinCFStringRefToString(CFNetServiceGetDomain(service), domain))
+ {
+ CLog::Log(LOGWARNING, "CZeroconfBrowserOSX::BrowserCallback failed to convert service strings.");
+ return;
+ }
+
+ ZeroconfService s(name, type, domain);
+
if (flags & kCFNetServiceFlagRemove)
{
CLog::Log(LOGDEBUG, "CZeroconfBrowserOSX::BrowserCallback service named: %s, type: %s, domain: %s disappeared",
// determine the correct artwork for this item
if (!thumb_loader.IsNull())
- thumb_loader->FillLibraryArt(item);
+ thumb_loader->LoadItem(&item);
// finally apply the found artwork
thumb = item.GetArt("thumb");
else
filename = URIUtils::GetFileName(file_path);
+ CURL::Encode(filename);
md5state.append(file_path);
md5state.getDigest(md5);
md5 += "/" + filename;
// Version.
//
- const char* Cocoa_GetAppVersion();
bool Cocoa_HasVDADecoder();
bool Cocoa_GPUForDisplayIsNvidiaPureVideo3();
int Cocoa_GetOSVersion();
}
//---------------------------------------------------------------------------------
-static char strVersion[32];
-
-const char* Cocoa_GetAppVersion()
-{
- // Get the main bundle for the app and return the version.
- CFBundleRef mainBundle = CFBundleGetMainBundle();
- CFStringRef versStr = (CFStringRef)CFBundleGetValueForInfoDictionaryKey(mainBundle, kCFBundleVersionKey);
-
- memset(strVersion,0,32);
-
- if (versStr != NULL && CFGetTypeID(versStr) == CFStringGetTypeID())
- {
- bool res = CFStringGetCString(versStr, strVersion, 32,kCFStringEncodingUTF8);
- if (!res)
- {
- printf("Error converting version string\n");
- strcpy(strVersion, "SVN");
- }
- }
- else
- strcpy(strVersion, "SVN");
-
- return strVersion;
-}
-
bool Cocoa_HasVDADecoder()
{
static int result = -1;
#include <string>
+// We forward declare CFStringRef in order to avoid
+// pulling in tons of Objective-C headers.
+struct __CFString;
+typedef const struct __CFString * CFStringRef;
+
#ifdef __cplusplus
extern "C"
{
bool DarwinHasVideoToolboxDecoder(void);
int DarwinBatteryLevel(void);
void DarwinSetScheduling(int message);
+ bool DarwinCFStringRefToString(CFStringRef source, std::string& destination);
#ifdef __cplusplus
}
#endif
#import <sys/sysctl.h>
#else
#import <Cocoa/Cocoa.h>
+ #import <CoreFoundation/CoreFoundation.h>
#import <IOKit/ps/IOPowerSources.h>
#import <IOKit/ps/IOPSKeys.h>
#endif
result = pthread_setschedparam(this_pthread_self, policy, ¶m );
}
+bool DarwinCFStringRefToString(CFStringRef source, std::string &destination)
+{
+ const char *cstr = CFStringGetCStringPtr(source, CFStringGetSystemEncoding());
+ if (!cstr)
+ {
+ CFIndex strLen = CFStringGetMaximumSizeForEncoding(CFStringGetLength(source) + 1,
+ CFStringGetSystemEncoding());
+ char *allocStr = (char*)malloc(strLen);
+
+ if(!allocStr)
+ return false;
+
+ if(!CFStringGetCString(source, allocStr, strLen, CFStringGetSystemEncoding()))
+ {
+ free((void*)allocStr);
+ return false;
+ }
+
+ destination = allocStr;
+ free((void*)allocStr);
+
+ return true;
+ }
+
+ destination = cstr;
+ return true;
+}
+
#endif
#define kCGEventTapOptionDefault 0
#endif
-#define NX_KEYSTATE_UP 0x0A
-#define NX_KEYSTATE_DOWN 0x0B
+#define NX_KEYSTATE_DOWN 0x0A
+#define NX_KEYSTATE_UP 0x0B
@implementation HotKeyController
int keyState = (keyFlags & 0xFF00) >> 8;
BOOL keyIsRepeat = (keyFlags & 0x1) > 0;
- if (keyIsRepeat)
+ // allow repeated keypresses for volume buttons
+ // all other repeated keypresses are handled by the os (is this really good?)
+ if (keyIsRepeat && keyCode != NX_KEYTYPE_SOUND_UP && keyCode != NX_KEYTYPE_SOUND_DOWN)
return event;
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
m_active = NO;
m_eventPort = NULL;
m_runLoopSource = NULL;
- m_controlSysPower = NO;
- m_controlSysVolume = NO;
+ // power button controls xbmc sleep button (this will also trigger the osx shutdown menu - we can't prevent this as it seems)
+ m_controlSysPower = YES;
+ m_controlSysVolume = YES; // volume keys control xbmc volume
}
return self;
}
#include "PeripheralBusUSB.h"
#include "peripherals/Peripherals.h"
#include "utils/log.h"
+#include "osx/DarwinUtils.h"
#include <sys/param.h>
result = (*interfaceInterface)->GetInterfaceClass(interfaceInterface, &bInterfaceClass);
if (bInterfaceClass == kUSBHIDInterfaceClass || bInterfaceClass == kUSBCommunicationDataInterfaceClass)
{
- char ttlDeviceFilePath[MAXPATHLEN] = {0};
+ std::string ttlDeviceFilePath;
CFStringRef deviceFilePathAsCFString;
USBDevicePrivateData *privateDataRef;
privateDataRef = new USBDevicePrivateData;
kIOServicePlane, CFSTR(kIOCalloutDeviceKey), kCFAllocatorDefault, kIORegistryIterateRecursively);
if (deviceFilePathAsCFString)
{
- // Convert the path from a CFString to a NULL-terminated C string
- CFStringGetCString((CFStringRef)deviceFilePathAsCFString,
- ttlDeviceFilePath, MAXPATHLEN - 1, kCFStringEncodingASCII);
+ // Convert the path from a CFString to a std::string
+ if (!DarwinCFStringRefToString(deviceFilePathAsCFString, ttlDeviceFilePath))
+ CLog::Log(LOGWARNING, "CPeripheralBusUSB::DeviceAttachCallback failed to convert CFStringRef");
CFRelease(deviceFilePathAsCFString);
}
IOObjectRelease(parent);
}
}
- if (strlen(ttlDeviceFilePath))
- privateDataRef->result.m_strLocation.Format("%s", ttlDeviceFilePath);
+ if (!ttlDeviceFilePath.empty())
+ privateDataRef->result.m_strLocation.Format("%s", ttlDeviceFilePath.c_str());
else
privateDataRef->result.m_strLocation.Format("%d", locationId);
CSingleLock lock(m_critSection);
bActivateSource = (m_configuration.bActivateSource &&
!m_bOnPlayReceived &&
+ !m_cecAdapter->IsLibCECActiveSource() &&
(!m_preventActivateSourceOnPlay.IsValid() || CDateTime::GetCurrentDateTime() - m_preventActivateSourceOnPlay > CDateTimeSpan(0, 0, 0, CEC_SUPPRESS_ACTIVATE_SOURCE_AFTER_ON_STOP)));
m_bOnPlayReceived = true;
}
{
// display warning: libCEC could not be loaded
CLog::Log(LOGERROR, "%s", g_localizeStrings.Get(36017).c_str());
- CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(36000), g_localizeStrings.Get(36029));
+ CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(36000), g_localizeStrings.Get(36017));
delete m_dll;
m_dll = NULL;
m_features.clear();
bool CPicture::OrientateImage(uint32_t *&pixels, unsigned int &width, unsigned int &height, int orientation)
{
// ideas for speeding these functions up: http://cgit.freedesktop.org/pixman/tree/pixman/pixman-fast-path.c
- uint32_t *out = NULL;
+ bool out = false;
switch (orientation)
{
case 1:
CLog::Log(LOGERROR, "Unknown orientation %i", orientation);
break;
}
- if (out)
- {
- pixels = out;
- std::swap(width, height);
- return true;
- }
- return false;
+ return out;
}
-uint32_t *CPicture::FlipHorizontal(uint32_t *pixels, unsigned int width, unsigned int height)
+bool CPicture::FlipHorizontal(uint32_t *&pixels, unsigned int &width, unsigned int &height)
{
// this can be done in-place easily enough
for (unsigned int y = 0; y < height; ++y)
for (unsigned int x = 0; x < width / 2; ++x)
std::swap(line[x], line[width - 1 - x]);
}
- return NULL;
+ return true;
}
-uint32_t *CPicture::FlipVertical(uint32_t *pixels, unsigned int width, unsigned int height)
+bool CPicture::FlipVertical(uint32_t *&pixels, unsigned int &width, unsigned int &height)
{
// this can be done in-place easily enough
for (unsigned int y = 0; y < height / 2; ++y)
for (unsigned int x = 0; x < width; ++x)
std::swap(*line1++, *line2++);
}
- return NULL;
+ return true;
}
-uint32_t *CPicture::Rotate180CCW(uint32_t *pixels, unsigned int width, unsigned int height)
+bool CPicture::Rotate180CCW(uint32_t *&pixels, unsigned int &width, unsigned int &height)
{
// this can be done in-place easily enough
for (unsigned int y = 0; y < height / 2; ++y)
for (unsigned int x = 0; x < width / 2; ++x)
std::swap(line[x], line[width - 1 - x]);
}
- return NULL;
+ return true;
}
-uint32_t *CPicture::Rotate90CCW(uint32_t *pixels, unsigned int width, unsigned int height)
+bool CPicture::Rotate90CCW(uint32_t *&pixels, unsigned int &width, unsigned int &height)
{
uint32_t *dest = new uint32_t[width * height * 4];
if (dest)
src += width;
}
}
+ delete[] pixels;
+ pixels = dest;
+ std::swap(width, height);
+ return true;
}
- delete[] pixels;
- return dest;
+ return false;
}
-uint32_t *CPicture::Rotate270CCW(uint32_t *pixels, unsigned int width, unsigned int height)
+bool CPicture::Rotate270CCW(uint32_t *&pixels, unsigned int &width, unsigned int &height)
{
uint32_t *dest = new uint32_t[width * height * 4];
if (!dest)
- return NULL;
+ return false;
unsigned int d_height = width, d_width = height;
for (unsigned int y = 0; y < d_height; y++)
}
delete[] pixels;
- return dest;
+ pixels = dest;
+ std::swap(width, height);
+ return true;
}
-uint32_t *CPicture::Transpose(uint32_t *pixels, unsigned int width, unsigned int height)
+bool CPicture::Transpose(uint32_t *&pixels, unsigned int &width, unsigned int &height)
{
uint32_t *dest = new uint32_t[width * height * 4];
if (!dest)
- return NULL;
+ return false;
unsigned int d_height = width, d_width = height;
for (unsigned int y = 0; y < d_height; y++)
}
delete[] pixels;
- return dest;
+ pixels = dest;
+ std::swap(width, height);
+ return true;
}
-uint32_t *CPicture::TransposeOffAxis(uint32_t *pixels, unsigned int width, unsigned int height)
+bool CPicture::TransposeOffAxis(uint32_t *&pixels, unsigned int &width, unsigned int &height)
{
uint32_t *dest = new uint32_t[width * height * 4];
if (!dest)
- return NULL;
+ return false;
unsigned int d_height = width, d_width = height;
for (unsigned int y = 0; y < d_height; y++)
}
delete[] pixels;
- return dest;
+ pixels = dest;
+ std::swap(width, height);
+ return true;
}
uint8_t *out_pixels, unsigned int out_width, unsigned int out_height, unsigned int out_pitch);
static bool OrientateImage(uint32_t *&pixels, unsigned int &width, unsigned int &height, int orientation);
- static uint32_t *FlipHorizontal(uint32_t *pixels, unsigned int width, unsigned int height);
- static uint32_t *FlipVertical(uint32_t *pixels, unsigned int width, unsigned int height);
- static uint32_t *Rotate90CCW(uint32_t *pixels, unsigned int width, unsigned int height);
- static uint32_t *Rotate270CCW(uint32_t *pixels, unsigned int width, unsigned int height);
- static uint32_t *Rotate180CCW(uint32_t *pixels, unsigned int width, unsigned int height);
- static uint32_t *Transpose(uint32_t *pixels, unsigned int width, unsigned int height);
- static uint32_t *TransposeOffAxis(uint32_t *pixels, unsigned int width, unsigned int height);
+ static bool FlipHorizontal(uint32_t *&pixels, unsigned int &width, unsigned int &height);
+ static bool FlipVertical(uint32_t *&pixels, unsigned int &width, unsigned int &height);
+ static bool Rotate90CCW(uint32_t *&pixels, unsigned int &width, unsigned int &height);
+ static bool Rotate270CCW(uint32_t *&pixels, unsigned int &width, unsigned int &height);
+ static bool Rotate180CCW(uint32_t *&pixels, unsigned int &width, unsigned int &height);
+ static bool Transpose(uint32_t *&pixels, unsigned int &width, unsigned int &height);
+ static bool TransposeOffAxis(uint32_t *&pixels, unsigned int &width, unsigned int &height);
};
//this class calls CreateThumbnailFromSurface in a CJob, so a png file can be written without halting the render thread
#include "system.h"
#include "PowerManager.h"
#include "Application.h"
+#include "cores/AudioEngine/AEFactory.h"
#include "input/KeyboardStat.h"
#include "settings/GUISettings.h"
#include "windowing/WindowingFactory.h"
g_application.StopPlaying();
g_application.StopShutdownTimer();
g_application.StopScreenSaverTimer();
+ CAEFactory::Suspend();
}
void CPowerManager::OnWake()
#if defined(HAS_SDL) || defined(TARGET_WINDOWS)
if (g_Windowing.IsFullScreen())
{
-#ifdef _WIN32
+#if defined(_WIN32)
ShowWindow(g_hWnd,SW_RESTORE);
SetForegroundWindow(g_hWnd);
-#else
+#elif !defined(TARGET_DARWIN_OSX)
// Hack to reclaim focus, thus rehiding system mouse pointer.
// Surely there's a better way?
g_graphicsContext.ToggleFullScreenRoot();
g_lcd->Initialize();
#endif
+ CAEFactory::Resume();
g_application.UpdateLibraries();
g_weatherManager.Refresh();
if (!group.IsInternalGroup())
{
/* First remove channels that don't exist in the main channels table */
- CStdString strWhereClause = FormatSQL("idChannel IN (SELECT m.idChannel FROM map_channelgroups_channels m LEFT JOIN channels on m.idChannel = channels.idChannel WHERE channels.idChannel IS NULL)");
- bDelete = DeleteValues("map_channelgroups_channels", strWhereClause);
+
+ // XXX work around for frodo: fix this up so it uses one query for all db types
+ // mysql doesn't support subqueries when deleting and sqlite doesn't support joins when deleting
+ if (g_advancedSettings.m_databaseTV.type.Equals("mysql"))
+ {
+ CStdString strQuery = FormatSQL("DELETE m FROM map_channelgroups_channels m LEFT JOIN channels c ON (c.idChannel = m.idChannel) WHERE c.idChannel IS NULL");
+ bDelete = ExecuteQuery(strQuery);
+ }
+ else
+ {
+ CStdString strWhereClause = FormatSQL("idChannel IN (SELECT m.idChannel FROM map_channelgroups_channels m LEFT JOIN channels on m.idChannel = channels.idChannel WHERE channels.idChannel IS NULL)");
+ bDelete = DeleteValues("map_channelgroups_channels", strWhereClause);
+ }
}
if (group.m_members.size() > 0)
{
CPVRChannelGroupPtr currentGroup = SelectedGroup();
CPVRChannelGroupPtr nextGroup = currentGroup->GetNextGroup();
- while (nextGroup && *nextGroup != *currentGroup && nextGroup->Size() == 0)
+ while (nextGroup && nextGroup->Size() == 0 &&
+ // break if the group matches
+ *nextGroup != *currentGroup &&
+ // or if we hit the first group
+ !nextGroup->IsInternalGroup())
nextGroup = nextGroup->GetNextGroup();
/* always update so users can reset the list */
{
std::string cachedSeason = GetCachedSeasonThumb(j->first, item->GetVideoInfoTag()->m_strPath);
std::string type;
- if (CacheTexture(j->second.begin()->second, cachedSeason, "", type))
- db.SetArtForItem(idSeason, "season", type, j->second.begin()->second);
+ std::string originalUrl = j->second.begin()->second;
+ if (CacheTexture(originalUrl, cachedSeason, "", type))
+ db.SetArtForItem(idSeason, "season", type, originalUrl);
}
}
items.Clear();
}
-bool CEdenVideoArtUpdater::CacheTexture(const std::string &originalUrl, const std::string &cachedFile, const std::string &label)
+bool CEdenVideoArtUpdater::CacheTexture(std::string &originalUrl, const std::string &cachedFile, const std::string &label)
{
std::string type;
return CacheTexture(originalUrl, cachedFile, label, type);
}
-bool CEdenVideoArtUpdater::CacheTexture(const std::string &originalUrl, const std::string &cachedFile, const std::string &label, std::string &type)
+bool CEdenVideoArtUpdater::CacheTexture(std::string &originalUrl, const std::string &cachedFile, const std::string &label, std::string &type)
{
- if (originalUrl.empty())
- {
- CLog::Log(LOGERROR, "%s No original url for item %s", __FUNCTION__, label.c_str());
- return false;
- }
if (!CFile::Exists(cachedFile))
{
CLog::Log(LOGERROR, "%s No cached art for item %s (should be %s)", __FUNCTION__, label.c_str(), cachedFile.c_str());
return false;
}
+ if (originalUrl.empty())
+ {
+ originalUrl = GetThumb(cachedFile, "http://unknown/video/", true);
+ CLog::Log(LOGERROR, "%s No original url for item %s, but cached art exists, using %s", __FUNCTION__, label.c_str(), originalUrl.c_str());
+ }
CTextureDetails details;
details.updateable = false;
private:
/*! \brief Caches the texture from oldCachedFile as if it came from originalUrl into the texture cache.
- \param originalUrl the url that we think the oldCachedFile came from.
+ \param originalUrl [in/out] the url that we think the oldCachedFile came from. May be set if it's empty and an oldCachedFile exists.
\param oldCachedFile the old cached file
\param label the label of the item for logging
\param type [out] the type of art (poster/banner/thumb)
*/
- bool CacheTexture(const std::string &originalUrl, const std::string &cachedFile, const std::string &label, std::string &type);
- bool CacheTexture(const std::string &originalUrl, const std::string &oldCachedFile, const std::string &label);
+ bool CacheTexture(std::string &originalUrl, const std::string &cachedFile, const std::string &label, std::string &type);
+ bool CacheTexture(std::string &originalUrl, const std::string &oldCachedFile, const std::string &label);
CStdString GetCachedActorThumb(const CFileItem &item);
CStdString GetCachedSeasonThumb(int season, const CStdString &path);
}
}
}
-
+ }
+ if (singleFiles)
+ {
xmlDoc.Clear();
TiXmlDeclaration decl("1.0", "UTF-8", "yes");
xmlDoc.InsertEndChild(decl);
}
- if (singleFiles && images && !bSkip)
+ if (images && !bSkip)
{
+ if (!singleFiles)
+ {
+ CStdString strFileName(movie.m_strTitle);
+ if (movie.m_iYear > 0)
+ strFileName.AppendFormat("_%i", movie.m_iYear);
+ item.SetPath(GetSafeFile(moviesDir, strFileName) + ".avi");
+ }
for (map<string, string>::const_iterator i = artwork.begin(); i != artwork.end(); ++i)
{
CStdString savedThumb = item.GetLocalArt(i->first, false);
}
}
}
-
+ }
+ if (singleFiles)
+ {
xmlDoc.Clear();
TiXmlDeclaration decl("1.0", "UTF-8", "yes");
xmlDoc.InsertEndChild(decl);
}
- if (singleFiles && images && !bSkip)
+ if (images && !bSkip)
{
+ if (!singleFiles)
+ {
+ CStdString strFileName(StringUtils::Join(movie.m_artist, g_advancedSettings.m_videoItemSeparator) + "." + movie.m_strTitle);
+ if (movie.m_iYear > 0)
+ strFileName.AppendFormat("_%i", movie.m_iYear);
+ item.SetPath(GetSafeFile(moviesDir, strFileName) + ".avi");
+ }
for (map<string, string>::const_iterator i = artwork.begin(); i != artwork.end(); ++i)
{
CStdString savedThumb = item.GetLocalArt(i->first, false);
}
}
}
-
+ }
+ if (singleFiles)
+ {
xmlDoc.Clear();
TiXmlDeclaration decl("1.0", "UTF-8", "yes");
xmlDoc.InsertEndChild(decl);
}
- if (singleFiles && images && !bSkip)
+ if (images && !bSkip)
{
+ if (!singleFiles)
+ item.SetPath(GetSafeFile(tvshowsDir, tvshow.m_strTitle));
+
for (map<string, string>::const_iterator i = artwork.begin(); i != artwork.end(); ++i)
{
CStdString savedThumb = item.GetLocalArt(i->first, true);
}
}
}
-
+ }
+ if (singleFiles)
+ {
xmlDoc.Clear();
TiXmlDeclaration decl("1.0", "UTF-8", "yes");
xmlDoc.InsertEndChild(decl);
}
- if (singleFiles && images && !bSkip)
+ if (images && !bSkip)
{
+ if (!singleFiles)
+ {
+ CStdString epName;
+ epName.Format("s%02ie%02i.avi", episode.m_iSeason, episode.m_iEpisode);
+ item.SetPath(URIUtils::AddFileToFolder(showDir, epName));
+ }
for (map<string, string>::const_iterator i = artwork.begin(); i != artwork.end(); ++i)
{
CStdString savedThumb = item.GetLocalArt(i->first, false);
{
info.Load(movie);
CFileItem item(info);
- map<string, string> artwork;
- if (ImportArtFromXML(movie->FirstChild("art"), artwork))
- item.SetArt(artwork);
bool useFolders = info.m_basePath.IsEmpty() ? LookupByFolders(item.GetPath()) : false;
+ CStdString filename = info.m_strTitle;
+ if (info.m_iYear > 0)
+ filename.AppendFormat("_%i", info.m_iYear);
+ CFileItem artItem(item);
+ artItem.SetPath(GetSafeFile(moviesDir, filename) + ".avi");
+ scanner.GetArtwork(&artItem, CONTENT_MOVIES, useFolders, true, actorsDir);
+ item.SetArt(artItem.GetArt());
scanner.AddVideo(&item, CONTENT_MOVIES, useFolders, true, NULL, true);
- CStdString strFileName(info.m_strTitle);
- if (iVersion >= 1 && info.m_iYear > 0)
- strFileName.AppendFormat("_%i", info.m_iYear);
current++;
}
else if (strnicmp(movie->Value(), "musicvideo", 10) == 0)
{
info.Load(movie);
CFileItem item(info);
- map<string, string> artwork;
- if (ImportArtFromXML(movie->FirstChild("art"), artwork))
- item.SetArt(artwork);
bool useFolders = info.m_basePath.IsEmpty() ? LookupByFolders(item.GetPath()) : false;
+ CStdString filename = StringUtils::Join(info.m_artist, g_advancedSettings.m_videoItemSeparator) + "." + info.m_strTitle;
+ if (info.m_iYear > 0)
+ filename.AppendFormat("_%i", info.m_iYear);
+ CFileItem artItem(item);
+ artItem.SetPath(GetSafeFile(musicvideosDir, filename) + ".avi");
+ scanner.GetArtwork(&artItem, CONTENT_MOVIES, useFolders, true, actorsDir);
+ item.SetArt(artItem.GetArt());
scanner.AddVideo(&item, CONTENT_MUSICVIDEOS, useFolders, true, NULL, true);
- CStdString strFileName(StringUtils::Join(info.m_artist, g_advancedSettings.m_videoItemSeparator) + "." + info.m_strTitle);
- if (iVersion >= 1 && info.m_iYear > 0)
- strFileName.AppendFormat("_%i", info.m_iYear);
current++;
}
else if (strnicmp(movie->Value(), "tvshow", 6) == 0)
URIUtils::AddSlashAtEnd(info.m_strPath);
DeleteTvShow(info.m_strPath);
CFileItem showItem(info);
- map<string, string> artwork;
- if (ImportArtFromXML(movie->FirstChild("art"), artwork))
- showItem.SetArt(artwork);
bool useFolders = info.m_basePath.IsEmpty() ? LookupByFolders(showItem.GetPath(), true) : false;
+ CFileItem artItem(showItem);
+ CStdString artPath(GetSafeFile(tvshowsDir, info.m_strTitle));
+ artItem.SetPath(artPath);
+ scanner.GetArtwork(&artItem, CONTENT_MOVIES, useFolders, true, actorsDir);
+ showItem.SetArt(artItem.GetArt());
int showID = scanner.AddVideo(&showItem, CONTENT_TVSHOWS, useFolders, true, NULL, true);
// season artwork
- TiXmlNode *art = movie->FirstChild("art");
- if (art)
+ map<int, map<string, string> > seasonArt;
+ artItem.GetVideoInfoTag()->m_strPath = artPath;
+ scanner.GetSeasonThumbs(*artItem.GetVideoInfoTag(), seasonArt, CVideoThumbLoader::GetArtTypes("season"), true);
+ for (map<int, map<string, string> >::iterator i = seasonArt.begin(); i != seasonArt.end(); ++i)
{
- TiXmlElement *season = art->FirstChildElement("season");
- while (season)
- {
- if (season->FirstChild())
- {
- int seasonNum = -1;
- season->Attribute("num", &seasonNum);
- map<string, string> artwork;
-
- int seasonID = AddSeason(showID, seasonNum);
- if (ImportArtFromXML(season, artwork) && seasonID > -1)
- SetArtForItem(seasonID, "season", artwork);
- }
- season = season->NextSiblingElement("season");
- }
+ int seasonID = AddSeason(showID, i->first);
+ SetArtForItem(seasonID, "season", i->second);
}
current++;
// now load the episodes
CVideoInfoTag info;
info.Load(episode);
CFileItem item(info);
- map<string, string> artwork;
- if (ImportArtFromXML(episode->FirstChild("art"), artwork))
- item.SetArt(artwork);
+ CStdString filename;
+ filename.Format("s%02ie%02i.avi", info.m_iSeason, info.m_iEpisode);
+ CFileItem artItem(item);
+ artItem.SetPath(GetSafeFile(artPath, filename));
+ scanner.GetArtwork(&artItem, CONTENT_MOVIES, useFolders, true, actorsDir);
+ item.SetArt(artItem.GetArt());
scanner.AddVideo(&item,CONTENT_TVSHOWS, false, false, showItem.GetVideoInfoTag(), true);
episode = episode->NextSiblingElement("episodedetails");
}
if (!m_database.Open())
return -1;
- GetArtwork(pItem, content, videoFolder, useLocal, showInfo ? showInfo->m_strPath : "");
+ if (!libraryImport)
+ GetArtwork(pItem, content, videoFolder, useLocal, showInfo ? showInfo->m_strPath : "");
+
// ensure the art map isn't completely empty by specifying an empty thumb
map<string, string> art = pItem->GetArt();
if (art.empty())
{
if (pItem->m_bIsFolder)
{
- // get and cache season thumbs
map<int, map<string, string> > seasonArt;
- GetSeasonThumbs(movieDetails, seasonArt, CVideoThumbLoader::GetArtTypes("season"), useLocal);
- for (map<int, map<string, string> >::iterator i = seasonArt.begin(); i != seasonArt.end(); ++i)
- for (map<string, string>::iterator j = i->second.begin(); j != i->second.end(); ++j)
- CTextureCache::Get().BackgroundCacheImage(j->second);
+ if (!libraryImport)
+ { // get and cache season thumbs
+ GetSeasonThumbs(movieDetails, seasonArt, CVideoThumbLoader::GetArtTypes("season"), useLocal);
+ for (map<int, map<string, string> >::iterator i = seasonArt.begin(); i != seasonArt.end(); ++i)
+ for (map<string, string>::iterator j = i->second.begin(); j != i->second.end(); ++j)
+ CTextureCache::Get().BackgroundCacheImage(j->second);
+ }
lResult = m_database.SetDetailsForTvShow(pItem->GetPath(), movieDetails, art, seasonArt);
movieDetails.m_iDbId = lResult;
movieDetails.m_type = "tvshow";
AC_INIT(configure.in)
AM_INIT_AUTOMAKE(goom2, 0.1)
-AM_CONFIG_HEADER(config.h)
+AC_CONFIG_HEADERS(config.h)
AC_ISC_POSIX
AC_PROG_CC
-AM_PROG_CC_STDC
AC_HEADER_STDC
AC_C_BIGENDIAN
void CWinIdleTimer::StartZero()
{
- SetThreadExecutionState(ES_SYSTEM_REQUIRED);
+ SetThreadExecutionState(ES_SYSTEM_REQUIRED|ES_DISPLAY_REQUIRED);
CStopWatch::StartZero();
}
, { 138, 0x69 /* 'i' */} // Info
, { 147, 0x6d /* 'm' */} // Menu
, { 148, XBMCK_LAUNCH_APP2 } // Launch app 2
-, { 150, 0x9f } // Sleep
+, { 150, XBMCK_SLEEP } // Sleep
, { 152, XBMCK_LAUNCH_APP1 } // Launch app 1
, { 163, XBMCK_LAUNCH_MAIL } // Launch Mail
, { 164, XBMCK_BROWSER_FAVORITES } // Browser favorites
m == 38 || m == 43 || m == 45 || m == 49 || m == 51 || \
m == 53 || m == 55 || m == 57 || m == 59)
-#define MAKEFLAGS(group, mode, interlace, mode3d) \
+#define MAKEFLAGS(group, mode, interlace) \
( ( (mode)<<24 ) | ( (group)<<16 ) | \
( (interlace) != 0 ? D3DPRESENTFLAG_INTERLACED : D3DPRESENTFLAG_PROGRESSIVE) | \
- ( ((group) == HDMI_RES_GROUP_CEA && IS_WIDESCREEN(mode) ) ? D3DPRESENTFLAG_WIDESCREEN : 0) | \
- ( (mode3d) != 0 ? D3DPRESENTFLAG_MODE3DSBS : 0 ))
+ ( ((group) == HDMI_RES_GROUP_CEA && IS_WIDESCREEN(mode) ) ? D3DPRESENTFLAG_WIDESCREEN : 0) )
-#define GETFLAGS_INTERLACE(f) ( ( (f) & D3DPRESENTFLAG_INTERLACED ) != 0)
-#define GETFLAGS_WIDESCREEN(f) ( ( (f) & D3DPRESENTFLAG_WIDESCREEN ) != 0)
#define GETFLAGS_GROUP(f) ( (HDMI_RES_GROUP_T)( ((f) >> 16) & 0xff ))
#define GETFLAGS_MODE(f) ( ( (f) >>24 ) & 0xff )
-#define GETFLAGS_MODE3D(f) ( ( (f) & D3DPRESENTFLAG_MODE3DSBS ) !=0 )
-
-#define TV_MAX_SUPPORTED_MODES 60
//#ifndef DEBUG_PRINT
//#define DEBUG_PRINT 1
#endif
}
-bool CEGLNativeTypeRaspberryPI::SetNativeResolution(const RESOLUTION_INFO &res)
+#if defined(TARGET_RASPBERRY_PI)
+int CEGLNativeTypeRaspberryPI::FindMatchingResolution(const RESOLUTION_INFO &res, const std::vector<RESOLUTION_INFO> &resolutions)
{
+ for (int i = 0; i < (int)resolutions.size(); i++)
+ {
+ if(resolutions[i].iScreenWidth == res.iScreenWidth && resolutions[i].iScreenHeight == res.iScreenHeight && resolutions[i].fRefreshRate == res.fRefreshRate)
+ {
+ return i;
+ }
+ }
+ return -1;
+}
+#endif
+
#if defined(TARGET_RASPBERRY_PI)
- bool bFound = false;
+int CEGLNativeTypeRaspberryPI::AddUniqueResolution(const RESOLUTION_INFO &res, std::vector<RESOLUTION_INFO> &resolutions)
+{
+ int i = FindMatchingResolution(res, resolutions);
+ if (i>=0)
+ { // don't replace a progressive resolution with an interlaced one of same resolution
+ if (!(res.dwFlags & D3DPRESENTFLAG_INTERLACED))
+ resolutions[i] = res;
+ }
+ else
+ {
+ resolutions.push_back(res);
+ }
+ return i;
+}
+#endif
+bool CEGLNativeTypeRaspberryPI::SetNativeResolution(const RESOLUTION_INFO &res)
+{
+#if defined(TARGET_RASPBERRY_PI)
if(!m_DllBcmHost || !m_nativeWindow)
return false;
DestroyDispmaxWindow();
- RESOLUTION_INFO resSearch;
-
- int best_score = 0;
-
- for (size_t i = 0; i < m_res.size(); i++)
- {
- if(m_res[i].iScreenWidth == res.iScreenWidth && m_res[i].iScreenHeight == res.iScreenHeight && m_res[i].fRefreshRate == res.fRefreshRate)
- {
- int score = 0;
-
- /* prefere progressive over interlaced */
- if(!GETFLAGS_INTERLACE(m_res[i].dwFlags))
- score = 1;
-
- if(score >= best_score)
- {
- resSearch = m_res[i];
- bFound = true;
- }
- }
- }
-
- if(bFound && !m_fixedMode)
+ if(!m_fixedMode && GETFLAGS_GROUP(res.dwFlags) && GETFLAGS_MODE(res.dwFlags))
{
sem_init(&m_tv_synced, 0, 0);
m_DllBcmHost->vc_tv_register_callback(CallbackTvServiceCallback, this);
- int success = m_DllBcmHost->vc_tv_hdmi_power_on_explicit(HDMI_MODE_HDMI, GETFLAGS_GROUP(resSearch.dwFlags), GETFLAGS_MODE(resSearch.dwFlags));
+ if (res.dwFlags & (D3DPRESENTFLAG_MODE3DSBS|D3DPRESENTFLAG_MODE3DTB))
+ {
+ /* inform TV of any 3D settings. Note this property just applies to next hdmi mode change, so no need to call for 2D modes */
+ HDMI_PROPERTY_PARAM_T property;
+ property.property = HDMI_PROPERTY_3D_STRUCTURE;
+ if (res.dwFlags & D3DPRESENTFLAG_MODE3DSBS)
+ property.param1 = HDMI_3D_FORMAT_SBS_HALF;
+ else if (res.dwFlags & D3DPRESENTFLAG_MODE3DTB)
+ property.param1 = HDMI_3D_FORMAT_TB_HALF;
+ else
+ property.param1 = HDMI_3D_FORMAT_NONE;
+ property.param2 = 0;
+ vc_tv_hdmi_set_property(&property);
+ }
+ int success = m_DllBcmHost->vc_tv_hdmi_power_on_explicit_new(HDMI_MODE_HDMI, GETFLAGS_GROUP(res.dwFlags), GETFLAGS_MODE(res.dwFlags));
if (success == 0)
{
- CLog::Log(LOGDEBUG, "EGL set HDMI mode (%d,%d,%d)=%d\n",
- GETFLAGS_MODE3D(resSearch.dwFlags) ? HDMI_MODE_3D:HDMI_MODE_HDMI, GETFLAGS_GROUP(resSearch.dwFlags),
- GETFLAGS_MODE(resSearch.dwFlags), success);
+ CLog::Log(LOGDEBUG, "EGL set HDMI mode (%d,%d)=%d%s%s\n",
+ GETFLAGS_GROUP(res.dwFlags), GETFLAGS_MODE(res.dwFlags), success,
+ (res.dwFlags & D3DPRESENTFLAG_MODE3DSBS) ? " SBS":"",
+ (res.dwFlags & D3DPRESENTFLAG_MODE3DTB) ? " TB":"");
+
sem_wait(&m_tv_synced);
}
else
{
- CLog::Log(LOGERROR, "EGL failed to set HDMI mode (%d,%d,%d)=%d\n",
- GETFLAGS_MODE3D(resSearch.dwFlags) ? HDMI_MODE_3D:HDMI_MODE_HDMI, GETFLAGS_GROUP(resSearch.dwFlags),
- GETFLAGS_MODE(resSearch.dwFlags), success);
+ CLog::Log(LOGERROR, "EGL failed to set HDMI mode (%d,%d)=%d%s%s\n",
+ GETFLAGS_GROUP(res.dwFlags), GETFLAGS_MODE(res.dwFlags), success,
+ (res.dwFlags & D3DPRESENTFLAG_MODE3DSBS) ? " SBS":"",
+ (res.dwFlags & D3DPRESENTFLAG_MODE3DTB) ? " TB":"");
}
m_DllBcmHost->vc_tv_unregister_callback(CallbackTvServiceCallback);
sem_destroy(&m_tv_synced);
- m_desktopRes = resSearch;
+ m_desktopRes = res;
}
m_dispman_display = m_DllBcmHost->vc_dispmanx_display_open(0);
DISPMANX_TRANSFORM_T transform = DISPMANX_NO_ROTATE;
DISPMANX_UPDATE_HANDLE_T dispman_update = m_DllBcmHost->vc_dispmanx_update_start(0);
- CLog::Log(LOGDEBUG, "EGL set resolution %dx%d -> %dx%d @ %.2f fps\n",
- m_width, m_height, dst_rect.width, dst_rect.height, bFound ? resSearch.fRefreshRate : res.fRefreshRate);
+ CLog::Log(LOGDEBUG, "EGL set resolution %dx%d -> %dx%d @ %.2f fps (%d,%d) flags:%x aspect:%.2f\n",
+ m_width, m_height, dst_rect.width, dst_rect.height, res.fRefreshRate, GETFLAGS_GROUP(res.dwFlags), GETFLAGS_MODE(res.dwFlags), (int)res.dwFlags, res.fPixelRatio);
// The trick for SBS is that we stick two dispman elements together
- // and the PI firmware knows that we are in SBS mode and it renders the gui in SBS
- if(bFound && (resSearch.dwFlags & D3DPRESENTFLAG_MODE3DSBS))
+ // and the PI firmware knows that we are in SBS/TAB mode and it renders the gui in SBS/TAB
+ if(res.dwFlags & (D3DPRESENTFLAG_MODE3DSBS|D3DPRESENTFLAG_MODE3DTB))
{
- // right side
- dst_rect.x = res.iScreenWidth;
- dst_rect.width = res.iScreenWidth;
+ if (res.dwFlags & D3DPRESENTFLAG_MODE3DTB)
+ {
+ // bottom half
+ dst_rect.y = res.iScreenHeight;
+ }
+ else
+ {
+ // right side
+ dst_rect.x = res.iScreenWidth;
+ }
m_dispman_element2 = m_DllBcmHost->vc_dispmanx_element_add(dispman_update,
m_dispman_display,
assert(m_dispman_element2 != DISPMANX_NO_HANDLE);
assert(m_dispman_element2 != (unsigned)DISPMANX_INVALID);
- // left side - fall through
- dst_rect.x = 0;
- dst_rect.width = res.iScreenWidth;
+ if (res.dwFlags & D3DPRESENTFLAG_MODE3DTB)
+ {
+ // top half - fall through
+ dst_rect.y = 0;
+ }
+ else
+ {
+ // left side - fall through
+ dst_rect.x = 0;
+ }
}
m_dispman_element = m_DllBcmHost->vc_dispmanx_element_add(dispman_update,
#endif
}
+#if defined(TARGET_RASPBERRY_PI)
+static float get_display_aspect_ratio(HDMI_ASPECT_T aspect)
+{
+ float display_aspect;
+ switch (aspect) {
+ case HDMI_ASPECT_4_3: display_aspect = 4.0/3.0; break;
+ case HDMI_ASPECT_14_9: display_aspect = 14.0/9.0; break;
+ case HDMI_ASPECT_16_9: display_aspect = 16.0/9.0; break;
+ case HDMI_ASPECT_5_4: display_aspect = 5.0/4.0; break;
+ case HDMI_ASPECT_16_10: display_aspect = 16.0/10.0; break;
+ case HDMI_ASPECT_15_9: display_aspect = 15.0/9.0; break;
+ case HDMI_ASPECT_64_27: display_aspect = 64.0/27.0; break;
+ default: display_aspect = 16.0/9.0; break;
+ }
+ return display_aspect;
+}
+
+static float get_display_aspect_ratio(SDTV_ASPECT_T aspect)
+{
+ float display_aspect;
+ switch (aspect) {
+ case SDTV_ASPECT_4_3: display_aspect = 4.0/3.0; break;
+ case SDTV_ASPECT_14_9: display_aspect = 14.0/9.0; break;
+ case SDTV_ASPECT_16_9: display_aspect = 16.0/9.0; break;
+ default: display_aspect = 4.0/3.0; break;
+ }
+ return display_aspect;
+}
+#endif
+
bool CEGLNativeTypeRaspberryPI::ProbeResolutions(std::vector<RESOLUTION_INFO> &resolutions)
{
#if defined(TARGET_RASPBERRY_PI)
resolutions.clear();
- m_res.clear();
if(!m_DllBcmHost)
return false;
if(m_initDesktopRes)
{
- TV_GET_STATE_RESP_T tv_state;
+ TV_DISPLAY_STATE_T tv_state;
// get current display settings state
- memset(&tv_state, 0, sizeof(TV_GET_STATE_RESP_T));
- m_DllBcmHost->vc_tv_get_state(&tv_state);
-
- m_desktopRes.iScreen = 0;
- m_desktopRes.bFullScreen = true;
- m_desktopRes.iWidth = tv_state.width;
- m_desktopRes.iHeight = tv_state.height;
- m_desktopRes.iScreenWidth = tv_state.width;
- m_desktopRes.iScreenHeight= tv_state.height;
- m_desktopRes.dwFlags = tv_state.scan_mode ? D3DPRESENTFLAG_INTERLACED : D3DPRESENTFLAG_PROGRESSIVE;
- m_desktopRes.fRefreshRate = (float)tv_state.frame_rate;
- m_desktopRes.strMode.Format("%dx%d", tv_state.width, tv_state.height);
- if((float)tv_state.frame_rate > 1)
+ memset(&tv_state, 0, sizeof(TV_DISPLAY_STATE_T));
+ m_DllBcmHost->vc_tv_get_display_state(&tv_state);
+
+ if ((tv_state.state & ( VC_HDMI_HDMI | VC_HDMI_DVI )) != 0) // hdtv
{
- m_desktopRes.strMode.Format("%s @ %.2f%s - Full Screen", m_desktopRes.strMode, (float)tv_state.frame_rate,
- m_desktopRes.dwFlags & D3DPRESENTFLAG_INTERLACED ? "i" : "");
+ m_desktopRes.iScreen = 0;
+ m_desktopRes.bFullScreen = true;
+ m_desktopRes.iWidth = tv_state.display.hdmi.width;
+ m_desktopRes.iHeight = tv_state.display.hdmi.height;
+ m_desktopRes.iScreenWidth = tv_state.display.hdmi.width;
+ m_desktopRes.iScreenHeight= tv_state.display.hdmi.height;
+ m_desktopRes.dwFlags = MAKEFLAGS(tv_state.display.hdmi.group, tv_state.display.hdmi.mode, tv_state.display.hdmi.scan_mode);
+ // Also add 3D flags
+ if (tv_state.display.hdmi.format_3d == HDMI_3D_FORMAT_SBS_HALF)
+ {
+ m_desktopRes.dwFlags |= D3DPRESENTFLAG_MODE3DSBS;
+ m_desktopRes.iWidth >>= 1;
+ m_desktopRes.iScreenWidth >>= 1;
+ }
+ else if (tv_state.display.hdmi.format_3d == HDMI_3D_FORMAT_TB_HALF)
+ {
+ m_desktopRes.dwFlags |= D3DPRESENTFLAG_MODE3DTB;
+ m_desktopRes.iHeight >>= 1;
+ m_desktopRes.iScreenHeight >>= 1;
+ }
+ m_desktopRes.fRefreshRate = (float)tv_state.display.hdmi.frame_rate;
+ m_desktopRes.fPixelRatio = get_display_aspect_ratio((HDMI_ASPECT_T)tv_state.display.hdmi.display_options.aspect) / ((float)m_desktopRes.iScreenWidth / (float)m_desktopRes.iScreenHeight);
+ }
+ else // sdtv
+ {
+ m_desktopRes.iScreen = 0;
+ m_desktopRes.bFullScreen = true;
+ m_desktopRes.iWidth = tv_state.display.sdtv.width;
+ m_desktopRes.iHeight = tv_state.display.sdtv.height;
+ m_desktopRes.iScreenWidth = tv_state.display.sdtv.width;
+ m_desktopRes.iScreenHeight= tv_state.display.sdtv.height;
+ m_desktopRes.dwFlags = D3DPRESENTFLAG_INTERLACED;
+ m_desktopRes.fRefreshRate = (float)tv_state.display.sdtv.frame_rate;
+ m_desktopRes.fPixelRatio = get_display_aspect_ratio((SDTV_ASPECT_T)tv_state.display.sdtv.display_options.aspect) / ((float)m_desktopRes.iScreenWidth / (float)m_desktopRes.iScreenHeight);
+ }
+
+ m_desktopRes.strMode.Format("%dx%d", m_desktopRes.iScreenWidth, m_desktopRes.iScreenHeight);
+
+ if((int)m_desktopRes.fRefreshRate > 1)
+ {
+ m_desktopRes.strMode.Format("%dx%d @ %.2f%s - Full Screen", m_desktopRes.iScreenWidth, m_desktopRes.iScreenHeight, m_desktopRes.fRefreshRate,
+ m_desktopRes.dwFlags & D3DPRESENTFLAG_INTERLACED ? "i" : "");
}
m_initDesktopRes = false;
m_desktopRes.iSubtitles = (int)(0.965 * m_desktopRes.iHeight);
- CLog::Log(LOGDEBUG, "EGL initial desktop resolution %s\n", m_desktopRes.strMode.c_str());
+ CLog::Log(LOGDEBUG, "EGL initial desktop resolution %s (%.2f)\n", m_desktopRes.strMode.c_str(), m_desktopRes.fPixelRatio);
}
-
GetSupportedModes(HDMI_RES_GROUP_CEA, resolutions);
GetSupportedModes(HDMI_RES_GROUP_DMT, resolutions);
- GetSupportedModes(HDMI_RES_GROUP_CEA_3D, resolutions);
if(resolutions.size() == 0)
{
- TV_GET_STATE_RESP_T tv;
- m_DllBcmHost->vc_tv_get_state(&tv);
-
RESOLUTION_INFO res;
- CLog::Log(LOGDEBUG, "EGL probe resolution %dx%d@%f %s:%x\n",
- m_desktopRes.iWidth, m_desktopRes.iHeight, m_desktopRes.fRefreshRate,
- m_desktopRes.dwFlags & D3DPRESENTFLAG_INTERLACED ? "i" : "p");
+ CLog::Log(LOGDEBUG, "EGL probe resolution %s:%x\n", m_desktopRes.strMode.c_str(), m_desktopRes.dwFlags);
- m_res.push_back(m_desktopRes);
- resolutions.push_back(m_desktopRes);
+ AddUniqueResolution(m_desktopRes, resolutions);
}
if(resolutions.size() < 2)
if(!m_DllBcmHost)
return;
- //Supported HDMI CEA/DMT resolutions, first one will be preferred resolution
- TV_SUPPORTED_MODE_T supported_modes[TV_MAX_SUPPORTED_MODES];
- int32_t num_modes;
+ //Supported HDMI CEA/DMT resolutions, preferred resolution will be returned
+ int32_t num_modes = 0;
HDMI_RES_GROUP_T prefer_group;
uint32_t prefer_mode;
int i;
+ TV_SUPPORTED_MODE_NEW_T *supported_modes = NULL;
+ // query the number of modes first
+ int max_supported_modes = m_DllBcmHost->vc_tv_hdmi_get_supported_modes_new(group, NULL, 0, &prefer_group, &prefer_mode);
- num_modes = m_DllBcmHost->vc_tv_hdmi_get_supported_modes(group,
- supported_modes, TV_MAX_SUPPORTED_MODES, &prefer_group, &prefer_mode);
+ if (max_supported_modes > 0)
+ supported_modes = new TV_SUPPORTED_MODE_NEW_T[max_supported_modes];
- CLog::Log(LOGDEBUG, "EGL get supported modes (%d) = %d, prefer_group=%x, prefer_mode=%x\n",
- group, num_modes, prefer_group, prefer_mode);
+ if (supported_modes)
+ {
+ num_modes = m_DllBcmHost->vc_tv_hdmi_get_supported_modes_new(group,
+ supported_modes, max_supported_modes, &prefer_group, &prefer_mode);
+
+ CLog::Log(LOGDEBUG, "EGL get supported modes (%d) = %d, prefer_group=%x, prefer_mode=%x\n",
+ group, num_modes, prefer_group, prefer_mode);
+ }
if (num_modes > 0 && prefer_group != HDMI_RES_GROUP_INVALID)
{
- TV_SUPPORTED_MODE_T *tv = supported_modes;
+ TV_SUPPORTED_MODE_NEW_T *tv = supported_modes;
for (i=0; i < num_modes; i++, tv++)
{
- // treat 3D modes as half-width SBS
- unsigned int width = (group == HDMI_RES_GROUP_CEA_3D) ? tv->width>>1 : tv->width;
RESOLUTION_INFO res;
- CLog::Log(LOGDEBUG, "EGL mode %d: %dx%d@%d %s%s:%x\n", i, width, tv->height, tv->frame_rate,
- tv->native ? "N" : "", tv->scan_mode ? "I" : "", tv->code);
res.iScreen = 0;
res.bFullScreen = true;
- res.dwFlags = MAKEFLAGS(group, tv->code, tv->scan_mode, group==HDMI_RES_GROUP_CEA_3D);
+ res.dwFlags = MAKEFLAGS(group, tv->code, tv->scan_mode);
res.fRefreshRate = (float)tv->frame_rate;
- res.fPixelRatio = 1.0f;
- res.iWidth = width;
+ res.iWidth = tv->width;
res.iHeight = tv->height;
- res.iScreenWidth = width;
+ res.iScreenWidth = tv->width;
res.iScreenHeight = tv->height;
- res.strMode.Format("%dx%d", width, tv->height);
- if((float)tv->frame_rate > 1)
- {
- res.strMode.Format("%dx%d @ %.2f%s - Full Screen", res.iScreenWidth, res.iScreenHeight, res.fRefreshRate,
- res.dwFlags & D3DPRESENTFLAG_INTERLACED ? "i" : "");
- }
+ res.fPixelRatio = get_display_aspect_ratio((HDMI_ASPECT_T)tv->aspect_ratio) / ((float)res.iScreenWidth / (float)res.iScreenHeight);
int gui_width = res.iWidth;
int gui_height = res.iHeight;
res.iWidth = gui_width;
res.iHeight = gui_height;
+ res.strMode.Format("%dx%d @ %.2f%s - Full Screen", res.iScreenWidth, res.iScreenHeight, res.fRefreshRate,
+ res.dwFlags & D3DPRESENTFLAG_INTERLACED ? "i" : "");
+
+ CLog::Log(LOGDEBUG, "EGL mode %d: %s (%.2f) %s%s:%x\n", i, res.strMode.c_str(), res.fPixelRatio,
+ tv->native ? "N" : "", tv->scan_mode ? "I" : "", tv->code);
+
res.iSubtitles = (int)(0.965 * res.iHeight);
- resolutions.push_back(res);
+ AddUniqueResolution(res, resolutions);
- /* replace initial desktop resolution with probed hdmi resolution */
- if(m_desktopRes.iWidth == res.iWidth && m_desktopRes.iHeight == res.iHeight &&
- m_desktopRes.iScreenWidth == res.iScreenWidth && m_desktopRes.iScreenHeight == res.iScreenHeight &&
- m_desktopRes.fRefreshRate == res.fRefreshRate)
+ // Also add 3D versions of modes
+ if (tv->struct_3d_mask & HDMI_3D_STRUCT_SIDE_BY_SIDE_HALF_HORIZONTAL)
{
- m_desktopRes = res;
- CLog::Log(LOGDEBUG, "EGL desktop replacement resolution %dx%d@%d %s%s:%x\n",
- width, tv->height, tv->frame_rate, tv->native ? "N" : "", tv->scan_mode ? "I" : "", tv->code);
+ RESOLUTION_INFO res2 = res;
+ res2.dwFlags |= D3DPRESENTFLAG_MODE3DSBS;
+ res2.iWidth >>= 1;
+ res2.iScreenWidth >>= 1;
+ res2.fPixelRatio = get_display_aspect_ratio((HDMI_ASPECT_T)tv->aspect_ratio) / ((float)res2.iScreenWidth / (float)res2.iScreenHeight);
+ res2.strMode.Format("%dx%d", res2.iScreenWidth, res2.iScreenHeight);
+ res2.strMode.Format("%dx%d @ %.2f%s - Full Screen", res2.iScreenWidth, res2.iScreenHeight, res2.fRefreshRate,
+ res2.dwFlags & D3DPRESENTFLAG_INTERLACED ? "i" : "");
+ CLog::Log(LOGDEBUG, "EGL mode %d: %s (%.2f) SBS\n", i, res2.strMode.c_str(), res2.fPixelRatio);
+
+ res2.iSubtitles = (int)(0.965 * res2.iHeight);
+
+ AddUniqueResolution(res2, resolutions);
+ }
+ if (tv->struct_3d_mask & HDMI_3D_STRUCT_TOP_AND_BOTTOM)
+ {
+ RESOLUTION_INFO res2 = res;
+ res2.dwFlags |= D3DPRESENTFLAG_MODE3DTB;
+ res2.iHeight >>= 1;
+ res2.iScreenHeight >>= 1;
+ res2.fPixelRatio = get_display_aspect_ratio((HDMI_ASPECT_T)tv->aspect_ratio) / ((float)res2.iScreenWidth / (float)res2.iScreenHeight);
+ res2.strMode.Format("%dx%d", res2.iScreenWidth, res2.iScreenHeight);
+ res2.strMode.Format("%dx%d @ %.2f%s - Full Screen", res2.iScreenWidth, res2.iScreenHeight, res2.fRefreshRate,
+ res2.dwFlags & D3DPRESENTFLAG_INTERLACED ? "i" : "");
+ CLog::Log(LOGDEBUG, "EGL mode %d: %s (%.2f) TAB\n", i, res2.strMode.c_str(), res2.fPixelRatio);
+
+ res2.iSubtitles = (int)(0.965 * res2.iHeight);
+
+ AddUniqueResolution(res2, resolutions);
}
-
- m_res.push_back(res);
}
}
+ if (supported_modes)
+ delete supported_modes;
}
void CEGLNativeTypeRaspberryPI::TvServiceCallback(uint32_t reason, uint32_t param1, uint32_t param2)
TV_GET_STATE_RESP_T m_tv_state;
sem_t m_tv_synced;
bool m_fixedMode;
- std::vector<RESOLUTION_INFO> m_res;
RESOLUTION_INFO m_desktopRes;
int m_width;
int m_height;
void DestroyDispmaxWindow();
bool ClampToGUIDisplayLimits(int &width, int &height);
+ int FindMatchingResolution(const RESOLUTION_INFO &res, const std::vector<RESOLUTION_INFO> &resolutions);
+ int AddUniqueResolution(const RESOLUTION_INFO &res, std::vector<RESOLUTION_INFO> &resolutions);
#endif
};
#include "WinSystemEGL.h"
#include "filesystem/SpecialProtocol.h"
#include "settings/Settings.h"
+#include "settings/GUISettings.h"
#include "utils/log.h"
#include "EGLWrapper.h"
#include "EGLQuirks.h"
return m_context;
}
+// the logic in this function should match whether CBaseRenderer::FindClosestResolution picks a 3D mode
bool CWinSystemEGL::Support3D(int width, int height, uint32_t mode) const
{
- bool bFound = false;
- int searchMode = 0;
- int searchWidth = width;
- int searchHeight = height;
+ RESOLUTION_INFO &curr = g_settings.m_ResInfo[g_graphicsContext.GetVideoResolution()];
- if (mode & D3DPRESENTFLAG_MODE3DSBS)
+ // if we are using automatic hdmi mode switching
+ if (g_guiSettings.GetInt("videoplayer.adjustrefreshrate") != ADJUST_REFRESHRATE_OFF)
{
- searchWidth /= 2;
- searchMode = D3DPRESENTFLAG_MODE3DSBS;
- }
- else if (mode & D3DPRESENTFLAG_MODE3DTB)
- {
- searchHeight /= 2;
- searchMode = D3DPRESENTFLAG_MODE3DTB;
- }
+ int searchWidth = curr.iScreenWidth;
+ int searchHeight = curr.iScreenHeight;
- for (unsigned int i = 0; i < g_settings.m_ResInfo.size(); i++)
+ // work out current non-3D resolution (in case we are currently in 3D mode)
+ if (curr.dwFlags & D3DPRESENTFLAG_MODE3DSBS)
+ {
+ searchWidth *= 2;
+ }
+ else if (curr.dwFlags & D3DPRESENTFLAG_MODE3DTB)
+ {
+ searchHeight *= 2;
+ }
+ // work out resolution if we switch to 3D mode
+ if (mode & D3DPRESENTFLAG_MODE3DSBS)
+ {
+ searchWidth /= 2;
+ }
+ else if (mode & D3DPRESENTFLAG_MODE3DTB)
+ {
+ searchHeight /= 2;
+ }
+ // only search the custom resolutions
+ for (unsigned int i = (int)RES_DESKTOP; i < g_settings.m_ResInfo.size(); i++)
+ {
+ RESOLUTION_INFO res = g_settings.m_ResInfo[i];
+ if(res.iScreenWidth == searchWidth && res.iScreenHeight == searchHeight && (res.dwFlags & mode))
+ return true;
+ }
+ }
+ // otherwise just consider current mode
+ else
{
- RESOLUTION_INFO res = g_settings.m_ResInfo[i];
-
- if(res.iWidth == searchWidth && res.iHeight == searchHeight && (res.dwFlags & searchMode))
- return true;
+ if (curr.dwFlags & mode)
+ return true;
}
- return bFound;
+ return false;
}
bool CWinSystemEGL::ClampToGUIDisplayLimits(int &width, int &height)
#include "osx/XBMCHelper.h"
#include "utils/SystemInfo.h"
#include "osx/CocoaInterface.h"
+#include "osx/DarwinUtils.h"
#undef BOOL
#import <SDL/SDL_video.h>
// default to false before we start parsing though the windows.
// if we are are obscured by any windows, then set true.
m_obscured = false;
+ static bool obscureLogged = false;
CGWindowListOption opts;
opts = kCGWindowListOptionOnScreenAboveWindow | kCGWindowListExcludeDesktopElements;
if (CFStringCompare(ownerName, CFSTR("Shades"), 0) == kCFCompareEqualTo ||
CFStringCompare(ownerName, CFSTR("SmartSaver"), 0) == kCFCompareEqualTo ||
CFStringCompare(ownerName, CFSTR("Brightness Slider"), 0) == kCFCompareEqualTo ||
- CFStringCompare(ownerName, CFSTR("Displaperture"), 0) == kCFCompareEqualTo)
+ CFStringCompare(ownerName, CFSTR("Displaperture"), 0) == kCFCompareEqualTo ||
+ CFStringCompare(ownerName, CFSTR("Dreamweaver"), 0) == kCFCompareEqualTo)
continue;
CFDictionaryRef rectDictionary = (CFDictionaryRef)CFDictionaryGetValue(windowDictionary, kCGWindowBounds);
if (CGRectContainsRect(windowBounds, bounds))
{
// if the windowBounds completely encloses our bounds, we are obscured.
+ if (!obscureLogged)
+ {
+ std::string appName;
+ if (DarwinCFStringRefToString(ownerName, appName))
+ CLog::Log(LOGDEBUG, "WinSystemOSX: Fullscreen window %s obscures XBMC!", appName.c_str());
+ obscureLogged = true;
+ }
m_obscured = true;
break;
}
if (!m_obscured)
{
+ // if we are here we are not obscured by any fullscreen window - reset flag
+ // for allowing the logmessage above to show again if this changes.
+ if (obscureLogged)
+ obscureLogged = false;
std::vector<CRect> rects = ourBounds.SubtractRects(partialOverlaps);
// they got us covered
if (rects.size() == 0)
if (!bEnable)
{
- CFStringRef reasonForActivity= CFSTR("XBMC requested disable system screen saver");
- IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep,
- kIOPMAssertionLevelOn, reasonForActivity, &assertionID);
+ if (assertionID == 0)
+ {
+ CFStringRef reasonForActivity= CFSTR("XBMC requested disable system screen saver");
+ IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep,
+ kIOPMAssertionLevelOn, reasonForActivity, &assertionID);
+ }
}
else if (assertionID != 0)
{
IOPMAssertionRelease(assertionID);
+ assertionID = 0;
}
m_use_system_screensaver = bEnable;
CLog::Log(LOGDEBUG, __FUNCTION__": Focus switched to process %s", procfile.c_str());
}
break;
+ /* needs to be reviewed after frodo. we reset the system idle timer
+ and the display timer directly now (see m_screenSaverTimer).
case WM_SYSCOMMAND:
switch( wParam&0xFFF0 )
{
case SC_SCREENSAVE:
return 0;
}
- break;
+ break;*/
case WM_SYSKEYDOWN:
switch (wParam)
{