diff options
author | waker <wakeroid@gmail.com> | 2010-10-31 18:25:37 +0100 |
---|---|---|
committer | waker <wakeroid@gmail.com> | 2010-10-31 18:25:37 +0100 |
commit | 86c756deeb6effd9b29968a3649f8387c0b6d62a (patch) | |
tree | 1911e2d105628de793f6c913c60f052b1f4e0a5a | |
parent | 1165a25a0ac57d3bcaf29afc9a5537523d4b0a99 (diff) | |
parent | 6e99c2322741baf2bbe50c69c29a0ffe4f2c8d0c (diff) |
Merge branch 'master' into static
Conflicts:
configure.ac
45 files changed, 6425 insertions, 429 deletions
@@ -1,3 +1,23 @@ +version 0.4.3 + fixed crash in OSS plugin + fixed random crashes caused by upgrading to libcurl-7.21.2 + fixed metadata editing in non-english locales + fixed switching playlists using hotkeys with caps/num/xcb modifiers + made preferences window tabs scrollable + fixed dts plugin description + fixed 'n' hotkey with numlock + improved support for icy (shoutcast) protocol + fixed ctrl+j (jump to current track) to work correctly with multiple playlists + fixed cursor/scroll follows playback to work correctly with multiple playlists + session resume does not seek anymore after skipping failed track + fixed seekbar flickering + bundled libmms library to fix freezes after updating to 0.6 + blank cue tracks (without titles) are not skipped anymore + fixed trailing whitespace handling in cuesheets + "middle mouse button to delete playlist" is now default behavior + fixed few issues with auto-saving playlist configuration + fixed playlist redraw after cddb queries + version 0.4.2 added translation into many languages, see translators.txt (LXDE Project) added ability to add custom menu items from plugins (Viktor Semykin) @@ -18,7 +38,7 @@ version 0.4.2 fixed reading of bad (unindented) CUE files new AAC decoder based on FAAD2 and MP4FF libraries new MMS plugin based on libmms, requires FFMPEG plugin to decode WMA content - new icon by Sofias <sofias@radikalismus.com> + new icon by Sofias new shorten plugin, port of xmms-shn new aosdk plugin, plays several PSF derivatives (PSF,PSF2,DSF,QSF,SSF,SPU) "stop after current" feature no longer does auto-reset every time @@ -2,17 +2,18 @@ pkgname=deadbeef pkgver=0.4.2 -pkgrel=1 +pkgrel=3 pkgdesc="mp3/ogg/flac/ape/sid/mod/nsf/m4a/mpc/shn music player based on GTK2" arch=(i686 x86_64) url="http://deadbeef.sourceforge.net" license=('GPL2') -makedepends=('gtk2', 'libsamplerate', 'libvorbis', 'libmad', 'flac', 'curl', 'alsa-lib', 'wavpack', 'libsndfile', 'libcdio', 'libcddb', 'ffmpeg', "libx11", "faad2", "zlib") +makedepends=('gtk2' 'libsamplerate' 'libvorbis' 'libmad' 'flac' 'curl' 'alsa-lib' 'wavpack' 'libsndfile' 'libcdio' 'libcddb' 'ffmpeg' 'libx11' 'faad2' 'zlib' 'intltool' 'pkgconfig') depends=('gtk2' 'libsamplerate' 'alsa-lib') optdepends=('libvorbis: ogg vorbis playback', 'libmad: mp1/2/3 playback', 'flac: flac playback', 'curl: lastfm scrobbler, shoutcast, icecast, podcast support', 'wavpack: wv playback', 'libsndfile: wav playback', "libcdio: audio cd plugin", "libcddb: audio cd plugin", "ffmpeg: for wma, aa3, oma, ac3, etc", "libmms: required for MMS protocol support", "faad2: required for AAC/MP4 support", "dbus: required for OSD notifications support", "pulseaudio: required for PulseAudio output plugin", "libx11: required for global hotkeys plugin", ) -makedepends=('pkgconfig') +install=deadbeef.install source=(http://downloads.sourceforge.net/project/$pkgname/$pkgname-$pkgver.tar.bz2) md5sums=('204ce66fd864b2e2b6241532cc53a024') +options=('!libtool') build() { cd $srcdir/$pkgname-$pkgver @@ -41,7 +41,6 @@ full list of dependencies: dbus: for notification daemon support (OSD current song notifications) pulseaudio: for PulseAudio output plugin faad2: for AAC plugin - libmms: for MMS protocol support zlib: for Audio Overload plugin (psf, psf2, etc) actual package names for your Linux distribution may vary. @@ -134,3 +134,10 @@ Copyright © 1999-2000 Aaron Holtzman <aholtzma@ess.engr.uvic.ca> xmms-shn 2.4.1 - Shorten XMMS Plugin (ported to Deadbeef API) http://etree.org/shnutils/xmms-shn/ Copyright © Jason Jordan <shnutils@freeshell.org> + + +libmms - Library to stream over mms/mmsh protocol +http://libmms.sourceforge.net/ +Copyright © Maciej Katafiasz (Mathrick) <mathrick@users.sourceforge.net> +Copyright © Søren Hansen (shawarma) <sh@warma.dk> + diff --git a/configure.ac b/configure.ac index 64d75c78..38455ae0 100644 --- a/configure.ac +++ b/configure.ac @@ -60,41 +60,41 @@ dnl INSANE_CXXFLAGS="-Wcomment -Wchar-subscripts -Wunused-function -Wunused-valu AC_SUBST(INSANE_CFLAGS) AC_SUBST(INSANE_CXXFLAGS) -AC_ARG_ENABLE(nullout, [ --disable-nullout disable NULL output plugin (default: enabled)], [enable_nullout=$enableval], [enable_nullout=yes]) -AC_ARG_ENABLE(alsa, [ --disable-alsa disable ALSA output plugin (default: enabled)], [enable_alsa=$enableval], [enable_alsa=yes]) -AC_ARG_ENABLE(oss, [ --disable-oss disable Open Sound System output plugin (default: enabled)], [enable_oss=$enableval], [enable_oss=yes]) -AC_ARG_ENABLE(pulse, [ --disable-pulse disable PulseAudio output plugin (default: enabled)], [enable_pulse=$enableval], [enable_pulse=yes]) -AC_ARG_ENABLE(gtkui, [ --disable-gtkui disable standard GTK2 user interface plugin (default: enabled)], [enable_gtkui=$enableval], [enable_gtkui=yes]) -AC_ARG_ENABLE(gtk3, [ --enable-gtk3 use GTK3 library (default: disabled)], [enable_gtk3=$enableval], [enable_gtk3=no]) -AC_ARG_ENABLE(vfs_curl, [ --disable-vfs-curl disable HTTP streaming vfs plugin (default: enabled)], [enable_vfs_curl=$enableval], [enable_vfs_curl=yes]) -AC_ARG_ENABLE(lfm, [ --disable-lfm disable last.fm/libre.fm scrobbler plugin (default: enabled)], [enable_lfm=$enableval], [enable_lfm=yes]) -AC_ARG_ENABLE(artwork, [ --disable-artwork disable album art loader plugin (default: enabled)], [enable_artwork=$enableval], [enable_artwork=yes]) -AC_ARG_ENABLE(supereq, [ --disable-supereq disable SuperEQ DSP plugin (default: enabled)], [enable_supereq=$enableval], [enable_supereq=yes]) -AC_ARG_ENABLE(sid, [ --disable-sid disable commodore64 SID music player plugin (default: enabled)], [enable_sid=$enableval], [enable_sid=yes]) -AC_ARG_ENABLE(mad, [ --disable-mpgmad disable mpeg (mad) plugin (default: enabled)], [enable_mpgmad=$enableval], [enable_mpgmad=yes]) -AC_ARG_ENABLE(ffap, [ --disable-ffap disable Monkey's Audio plugin (default: enabled)], [enable_ffap=$enableval], [enable_ffap=yes]) -AC_ARG_ENABLE(vtx, [ --disable-vtx disable libayemy VTX ZX-Spectrum music player plugin (default: enabled)], [enable_vtx=$enableval], [enable_vtx=yes]) -AC_ARG_ENABLE(adplug, [ --disable-adplug disable adplug plugin (default: enabled)], [enable_adplug=$enableval], [enable_adplug=yes]) -AC_ARG_ENABLE(hotkeys, [ --disable-hotkeys disable global hotkeys plugin (default: enabled)], [enable_hotkeys=$enableval], [enable_hotkeys=yes]) -AC_ARG_ENABLE(vorbis, [ --disable-vorbis disable Ogg Vorbis player plugin (default: enabled)], [enable_vorbis=$enableval], [enable_vorbis=yes]) -AC_ARG_ENABLE(ffmpeg, [ --disable-ffmpeg disable FFMPEG plugin for WMA, MPC, TTA, etc (default: enabled)], [enable_ffmpeg=$enableval], [enable_ffmpeg=yes]) -AC_ARG_ENABLE(flac, [ --disable-flac disable FLAC player plugin (default: enabled)], [enable_flac=$enableval], [enable_flac=yes]) -AC_ARG_ENABLE(sndfile, [ --disable-sndfile disable libsndfile plugin for PCM wave files (default: enabled)], [enable_sndfile=$enableval], [enable_sndfile=yes]) -AC_ARG_ENABLE(wavpack, [ --disable-wavpack disable wavpack plugin (default: enabled)], [enable_wavpack=$enableval], [enable_wavpack=yes]) -AC_ARG_ENABLE(cdda, [ --disable-cdda disable CD-Audio plugin (default: enabled)], [enable_cdda=$enableval], [enable_cdda=yes]) -AC_ARG_ENABLE(gme, [ --disable-gme disable Game Music Emu plugin for NSF, AY, etc (default: enabled)], [enable_gme=$enableval], [enable_gme=yes]) -AC_ARG_ENABLE(dumb, [ --disable-dumb disable D.U.M.B. plugin for MOD, S3M and other tracker formats (default: enabled)], [enable_dumb=$enableval], [enable_dumb=yes]) -AC_ARG_ENABLE(notify, [ --disable-notify disable notification-daemon support plugin (default: enabled)], [enable_notify=$enableval], [enable_notify=yes]) -AC_ARG_ENABLE(shellexec,[ --disable-shellexec disable shell commands plugin (default: enabled)], [enable_shellexec=$enableval], [enable_shellexec=yes]) -AC_ARG_ENABLE(musepack, [ --disable-musepack disable musepack plugin (default: enabled)], [enable_musepack=$enableval], [enable_musepack=yes]) -AC_ARG_ENABLE(wildmidi, [ --disable-wildmidi disable wildmidi plugin (default: enabled)], [enable_wildmidi=$enableval], [enable_wildmidi=yes]) -AC_ARG_ENABLE(tta, [ --disable-tta disable tta plugin (default: enabled)], [enable_tta=$enableval], [enable_tta=yes]) -AC_ARG_ENABLE(dca, [ --disable-dca disable dca (DTS audio) plugin (default: enabled)], [enable_dca=$enableval], [enable_dca=yes]) -AC_ARG_ENABLE(aac, [ --disable-aac disable AAC decoder based on FAAD2 (default: enabled)], [enable_aac=$enableval], [enable_aac=yes]) -AC_ARG_ENABLE(mms, [ --disable-mms disable MMS streaming vfs plugin (default: enabled)], [enable_mms=$enableval], [enable_mms=yes]) -AC_ARG_ENABLE(shn, [ --disable-shn disable shorten plugin (default: enabled)], [enable_shn=$enableval], [enable_shn=yes]) -AC_ARG_ENABLE(ao, [ --disable-ao disable audio overload plugin (default: enabled)], [enable_ao=$enableval], [enable_ao=yes]) -AC_ARG_ENABLE(mpris, [ --enable-mpris enable Ubuntu Sound Menu plugin (default: disabled)], [enable_mpris=$enableval], [enable_mpris=yes]) +AC_ARG_ENABLE(nullout, [AS_HELP_STRING([--disable-nullout ], [disable NULL output plugin (default: enabled)])], [enable_nullout=$enableval], [enable_nullout=yes]) +AC_ARG_ENABLE(alsa, [AS_HELP_STRING([--disable-alsa ], [disable ALSA output plugin (default: enabled)])], [enable_alsa=$enableval], [enable_alsa=yes]) +AC_ARG_ENABLE(oss, [AS_HELP_STRING([--disable-oss ], [disable Open Sound System output plugin (default: enabled)])], [enable_oss=$enableval], [enable_oss=yes]) +AC_ARG_ENABLE(pulse, [AS_HELP_STRING([--disable-pulse ], [disable PulseAudio output plugin (default: enabled)])], [enable_pulse=$enableval], [enable_pulse=yes]) +AC_ARG_ENABLE(gtkui, [AS_HELP_STRING([--disable-gtkui ], [disable standard GTK2 user interface plugin (default: enabled)])], [enable_gtkui=$enableval], [enable_gtkui=yes]) +AC_ARG_ENABLE(gtk3, [AS_HELP_STRING([--enable-gtk3 ], [use GTK3 library (default: disabled)])], [enable_gtk3=$enableval], [enable_gtk3=no]) +AC_ARG_ENABLE(vfs_curl, [AS_HELP_STRING([--disable-vfs-curl], [disable HTTP streaming vfs plugin (default: enabled)])], [enable_vfs_curl=$enableval], [enable_vfs_curl=yes]) +AC_ARG_ENABLE(lfm, [AS_HELP_STRING([--disable-lfm ], [disable last.fm/libre.fm scrobbler plugin (default: enabled)])], [enable_lfm=$enableval], [enable_lfm=yes]) +AC_ARG_ENABLE(artwork, [AS_HELP_STRING([--disable-artwork ], [disable album art loader plugin (default: enabled)])], [enable_artwork=$enableval], [enable_artwork=yes]) +AC_ARG_ENABLE(supereq, [AS_HELP_STRING([--disable-supereq ], [disable SuperEQ DSP plugin (default: enabled)])], [enable_supereq=$enableval], [enable_supereq=yes]) +AC_ARG_ENABLE(sid, [AS_HELP_STRING([--disable-sid ], [disable commodore64 SID music player plugin (default: enabled)])], [enable_sid=$enableval], [enable_sid=yes]) +AC_ARG_ENABLE(mad, [AS_HELP_STRING([--disable-mad ], [disable mpeg (mad) plugin (default: enabled)])], [enable_mpgmad=$enableval], [enable_mpgmad=yes]) +AC_ARG_ENABLE(ffap, [AS_HELP_STRING([--disable-ffap ], [disable Monkey's Audio plugin (default: enabled)])], [enable_ffap=$enableval], [enable_ffap=yes]) +AC_ARG_ENABLE(vtx, [AS_HELP_STRING([--disable-vtx ], [disable libayemy VTX ZX-Spectrum music player plugin (default: enabled)])], [enable_vtx=$enableval], [enable_vtx=yes]) +AC_ARG_ENABLE(adplug, [AS_HELP_STRING([--disable-adplug ], [disable adplug plugin (default: enabled)])], [enable_adplug=$enableval], [enable_adplug=yes]) +AC_ARG_ENABLE(hotkeys, [AS_HELP_STRING([--disable-hotkeys ], [disable global hotkeys plugin (default: enabled)])], [enable_hotkeys=$enableval], [enable_hotkeys=yes]) +AC_ARG_ENABLE(vorbis, [AS_HELP_STRING([--disable-vorbis ], [disable Ogg Vorbis player plugin (default: enabled)])], [enable_vorbis=$enableval], [enable_vorbis=yes]) +AC_ARG_ENABLE(ffmpeg, [AS_HELP_STRING([--disable-ffmpeg ], [disable FFMPEG plugin for WMA, MPC, TTA, etc (default: enabled)])], [enable_ffmpeg=$enableval], [enable_ffmpeg=yes]) +AC_ARG_ENABLE(flac, [AS_HELP_STRING([--disable-flac ], [disable FLAC player plugin (default: enabled)])], [enable_flac=$enableval], [enable_flac=yes]) +AC_ARG_ENABLE(sndfile, [AS_HELP_STRING([--disable-sndfile ], [disable libsndfile plugin for PCM wave files (default: enabled)])], [enable_sndfile=$enableval], [enable_sndfile=yes]) +AC_ARG_ENABLE(wavpack, [AS_HELP_STRING([--disable-wavpack ], [disable wavpack plugin (default: enabled)])], [enable_wavpack=$enableval], [enable_wavpack=yes]) +AC_ARG_ENABLE(cdda, [AS_HELP_STRING([--disable-cdda ], [disable CD-Audio plugin (default: enabled)])], [enable_cdda=$enableval], [enable_cdda=yes]) +AC_ARG_ENABLE(gme, [AS_HELP_STRING([--disable-gme ], [disable Game Music Emu plugin for NSF, AY, etc (default: enabled)])], [enable_gme=$enableval], [enable_gme=yes]) +AC_ARG_ENABLE(dumb, [AS_HELP_STRING([--disable-dumb ], [disable D.U.M.B. plugin for MOD, S3M and other tracker formats (default: enabled)])], [enable_dumb=$enableval], [enable_dumb=yes]) +AC_ARG_ENABLE(notify, [AS_HELP_STRING([--disable-notify ], [disable notification-daemon support plugin (default: enabled)])], [enable_notify=$enableval], [enable_notify=yes]) +AC_ARG_ENABLE(shellexec, [AS_HELP_STRING([--disable-shellexec], [disable shell commands plugin (default: enabled)])], [enable_shellexec=$enableval], [enable_shellexec=yes]) +AC_ARG_ENABLE(musepack, [AS_HELP_STRING([--disable-musepack], [disable musepack plugin (default: enabled)])], [enable_musepack=$enableval], [enable_musepack=yes]) +AC_ARG_ENABLE(wildmidi, [AS_HELP_STRING([--disable-wildmidi], [disable wildmidi plugin (default: enabled)])], [enable_wildmidi=$enableval], [enable_wildmidi=yes]) +AC_ARG_ENABLE(tta, [AS_HELP_STRING([--disable-tta ], [disable tta plugin (default: enabled)])], [enable_tta=$enableval], [enable_tta=yes]) +AC_ARG_ENABLE(dca, [AS_HELP_STRING([--disable-dca ], [disable dca (DTS audio) plugin (default: enabled)])], [enable_dca=$enableval], [enable_dca=yes]) +AC_ARG_ENABLE(aac, [AS_HELP_STRING([--disable-aac ], [disable AAC decoder based on FAAD2 (default: enabled)])], [enable_aac=$enableval], [enable_aac=yes]) +AC_ARG_ENABLE(mms, [AS_HELP_STRING([--disable-mms ], [disable MMS streaming vfs plugin (default: enabled)])], [enable_mms=$enableval], [enable_mms=yes]) +AC_ARG_ENABLE(shn, [AS_HELP_STRING([--disable-shn ], [disable shorten plugin (default: enabled)])], [enable_shn=$enableval], [enable_shn=yes]) +AC_ARG_ENABLE(ao, [AS_HELP_STRING([--disable-ao ], [disable audio overload plugin (default: enabled)])], [enable_ao=$enableval], [enable_ao=yes]) +AC_ARG_ENABLE(mpris, [ --enable-mpris enable Ubuntu Sound Menu plugin (default: disabled)], [enable_mpris=$enableval], [enable_mpris=no]) AC_ARG_ENABLE(portable, [ --enable-portable make portable static build (default: disabled)], [enable_portable=$enableval], [enable_portable=no]) if test "x$enable_portable" != "xno" ; then @@ -447,18 +447,9 @@ fi fi if test "x$enable_mms" != "xno" ; then -if test "x$enable_portable" != "xno" ; then - LIBMMS_LIBS="../../$LIB/libmms.a -lglib-2.0" + LIBMMS_LIBS="" AC_SUBST(LIBMMS_LIBS) HAVE_MMS=yes -else - AC_CHECK_LIB([mms], [main], [HAVE_LIBMMS=1]) - if test ${HAVE_LIBMMS} ; then - LIBMMS_LIBS="-lmms" - AC_SUBST(LIBMMS_LIBS) - HAVE_MMS=yes - fi -fi fi if test "x$enable_shn" != "xno" ; then @@ -424,12 +424,6 @@ typedef struct { int (*pl_format_title_escaped) (DB_playItem_t *it, int idx, char *s, int size, int id, const char *fmt); void (*pl_format_time) (float t, char *dur, int size); void (*pl_format_item_display_name) (DB_playItem_t *it, char *str, int len); -// void (*pl_set_next) (DB_playItem_t *it, DB_playItem_t *next, int iter); -// void (*pl_set_prev) (DB_playItem_t *it, DB_playItem_t *prev, int iter); -// void (*pl_set_head) (DB_playItem_t *it, int iter); -// void (*pl_set_tail) (DB_playItem_t *it, int iter); -// DB_playItem_t* (*pl_get_head) (void); -// DB_playItem_t* (*pl_get_tail) (void); void (*pl_move_items) (int iter, int plt_from, DB_playItem_t *drop_before, uint32_t *indexes, int count); void (*pl_copy_items) (int iter, int plt_from, DB_playItem_t *before, uint32_t *indices, int cnt); void (*pl_search_reset) (void); diff --git a/deadbeef.install b/deadbeef.install new file mode 100644 index 00000000..eaf1b9a6 --- /dev/null +++ b/deadbeef.install @@ -0,0 +1,14 @@ +pkgname=deadbeef + +post_install() { + gtk-update-icon-cache -q -t -f usr/share/icons/hicolor + update-desktop-database -q +} + +post_upgrade() { + post_install +} + +post_remove() { + post_install +} @@ -512,9 +512,11 @@ restore_resume_state (void) { int paused = conf_get_int ("resume.paused", 0); trace ("resume: track %d pos %f playlist %d\n", track, pos, plt); if (plt >= 0 && track >= 0 && pos >= 0) { + streamer_lock (); // need to hold streamer thread to make the resume operation atomic streamer_set_current_playlist (plt); - streamer_set_seek (pos); streamer_set_nextsong (track, paused ? 2 : 3); + streamer_set_seek (pos); + streamer_unlock (); } } } @@ -64,7 +64,7 @@ //#define trace(...) { fprintf(stderr, __VA_ARGS__); } #define trace(fmt,...) -#define SKIP_BLANK_CUE_TRACKS 1 +#define SKIP_BLANK_CUE_TRACKS 0 #define min(x,y) ((x)<(y)?(x):(y)) @@ -328,7 +328,7 @@ plt_add (int before, const char *title) { plt_gen_conf (); if (!plt_loading) { pl_save_n (before); - deadbeef->conf_save (); + conf_save (); plug_trigger_event (DB_EV_PLAYLISTSWITCH, 0); } return playlists_count-1; @@ -337,6 +337,7 @@ plt_add (int before, const char *title) { // NOTE: caller must ensure that configuration is saved after that call void plt_remove (int plt) { + trace ("plt_remove %d\n", plt); int i; assert (plt >= 0 && plt < playlists_count); PLT_LOCK; @@ -377,6 +378,8 @@ plt_remove (int plt) { playlist->title = strdup (_("Default")); PLT_UNLOCK; plt_gen_conf (); + conf_save (); + pl_save_n (0); plug_trigger_event (DB_EV_PLAYLISTSWITCH, 0); return; } @@ -404,6 +407,7 @@ plt_remove (int plt) { PLT_UNLOCK; plt_gen_conf (); + conf_save (); if (!plt_loading) { plug_trigger_event (DB_EV_PLAYLISTSWITCH, 0); } @@ -429,13 +433,6 @@ plt_set_curr (int plt) { for (i = 0; p && p->next && i < plt; i++) { p = p->next; } -#if 0 - if (i != plt) { - PLT_UNLOCK; - trace ("plt_set_curr %d failed\n", plt); - return; - } -#endif if (p != playlist) { playlist = p; if (!plt_loading) { @@ -518,6 +515,7 @@ plt_set_title (int plt, const char *title) { } PLT_UNLOCK; plt_gen_conf (); + conf_save (); if (!plt_loading) { plug_trigger_event (DB_EV_PLAYLISTSWITCH, 0); } @@ -681,7 +679,7 @@ plt_move (int from, int to) { PLT_UNLOCK; plt_gen_conf (); - deadbeef->conf_save (); + conf_save (); } void @@ -749,6 +747,9 @@ pl_get_value_from_cue (const char *p, int sz, char *out) { sz--; *out++ = *p++; } + while (out > p && (*(out-1) == 0x20 || *(out-1) == 0x8)) { + out--; + } *out = 0; } @@ -2022,7 +2023,12 @@ pl_save_n (int n) { PLT_UNLOCK; return -1; } + + playlist_t *orig = playlist; + int i; + for (i = 0, playlist = playlists_head; playlist && i < n; i++, playlist = playlist->next); err = pl_save (path); + playlist = orig; plt_loading = 0; PLT_UNLOCK; return err; diff --git a/plugins/aac/aac.c b/plugins/aac/aac.c index 2f6b518b..f7925b82 100644 --- a/plugins/aac/aac.c +++ b/plugins/aac/aac.c @@ -726,13 +726,17 @@ aac_read_int16 (DB_fileinfo_t *_info, char *bytes, int size) { sampleDuration, MP4_MSECS_TIME_SCALE); #endif if (info->mp4sample >= info->mp4samples) { + if (buffer) { + free (buffer); + } break; } info->mp4sample++; samples = NeAACDecDecode(info->dec, &frame_info, buffer, buffer_size); -#ifdef USE_MP4FF - free (buffer); -#endif + + if (buffer) { + free (buffer); + } if (!samples) { break; } diff --git a/plugins/aac/mp4ff/mp4ff.c b/plugins/aac/mp4ff/mp4ff.c index 9181ace9..ce33aad5 100644 --- a/plugins/aac/mp4ff/mp4ff.c +++ b/plugins/aac/mp4ff/mp4ff.c @@ -30,6 +30,7 @@ #include <stdlib.h> #include <string.h> +#include <stdio.h> #include "mp4ffint.h" mp4ff_t *mp4ff_open_read(mp4ff_callback_t *f) @@ -426,6 +427,10 @@ int32_t mp4ff_read_sample(mp4ff_t *f, const int32_t track, const int32_t sample, if (*bytes==0) return 0; *audio_buffer = (uint8_t*)malloc(*bytes); + if (!(*audio_buffer)) { + fprintf (stderr, "mp4ff_read_sample: malloc failure (tried to alloc %d bytes). possible mp4ff bug or memleak! please report a bug to deadbeef developers (i'm serious).\n", *bytes); + return 0; + } mp4ff_set_sample_position(f, track, sample); diff --git a/plugins/cdda/cdda.c b/plugins/cdda/cdda.c index f2e5066c..2ed54b51 100644 --- a/plugins/cdda/cdda.c +++ b/plugins/cdda/cdda.c @@ -362,6 +362,7 @@ cddb_thread (void *items_i) deadbeef->mutex_unlock (mutex); cleanup_thread_params (params); cddb_tid = 0; + deadbeef->plug_trigger_event_playlistchanged (); } static void @@ -384,15 +385,19 @@ read_track_cdtext (CdIo_t *cdio, int track_nr, DB_playItem_t *item) { switch (field_type) { - case CDTEXT_TITLE: album = strdup (text); break; - case CDTEXT_PERFORMER: artist = strdup (text); break; + case CDTEXT_TITLE: album = text; break; + case CDTEXT_PERFORMER: artist = text; break; } } } trace ("artist: %s; album: %s\n", artist, album); - deadbeef->pl_replace_meta (item, "artist", artist); - deadbeef->pl_replace_meta (item, "album", album); + if (artist) { + deadbeef->pl_replace_meta (item, "artist", artist); + } + if (album) { + deadbeef->pl_replace_meta (item, "album", album); + } cdtext = cdio_get_cdtext (cdio, track_nr); if (!cdtext) @@ -414,7 +419,7 @@ read_track_cdtext (CdIo_t *cdio, int track_nr, DB_playItem_t *item) case CDTEXT_MESSAGE: field = "comment"; break; default: field = NULL; } - if (field) + if (field && text) { trace ("%s: %s\n", field, text); deadbeef->pl_replace_meta (item, field, text); @@ -521,7 +526,6 @@ cda_insert (DB_playItem_t *after, const char *fname) { } cdio_destroy (cdio); } - deadbeef->plug_trigger_event_playlistchanged (); return res; } diff --git a/plugins/gtkui/callbacks.c b/plugins/gtkui/callbacks.c index 7ca76d0c..84a585ee 100644 --- a/plugins/gtkui/callbacks.c +++ b/plugins/gtkui/callbacks.c @@ -312,7 +312,7 @@ on_mainwin_key_press_event (GtkWidget *widget, gpointer user_data) { uint32_t maskedstate = (event->state &~ (GDK_LOCK_MASK | GDK_MOD2_MASK | GDK_MOD3_MASK | GDK_MOD5_MASK)) & 0xfff; - if (event->keyval == GDK_n && !event->state) { + if ((maskedstate == GDK_MOD1_MASK || maskedstate == 0) && event->keyval == GDK_n) { // button for that one is not in toolbar anymore, so handle it manually deadbeef->sendmessage (M_PLAYRANDOM, 0, 0, 0); } @@ -560,7 +560,6 @@ on_seekbar_button_release_event (GtkWidget *widget, GdkEventButton *event) { seekbar_moving = 0; - gtk_widget_queue_draw (widget); DB_playItem_t *trk = deadbeef->streamer_get_playing_track (); if (trk) { float time = (event->x - widget->allocation.x) * deadbeef->pl_get_item_duration (trk) / (widget->allocation.width); @@ -570,6 +569,7 @@ on_seekbar_button_release_event (GtkWidget *widget, deadbeef->streamer_seek (time); deadbeef->pl_item_unref (trk); } + gtk_widget_queue_draw (widget); return FALSE; } diff --git a/plugins/gtkui/ddbtabstrip.c b/plugins/gtkui/ddbtabstrip.c index 36b80443..ea7905c7 100644 --- a/plugins/gtkui/ddbtabstrip.c +++ b/plugins/gtkui/ddbtabstrip.c @@ -632,7 +632,6 @@ on_rename_playlist1_activate (GtkMenuItem *menuitem, if (res == GTK_RESPONSE_OK) { const char *text = gtk_entry_get_text (GTK_ENTRY (e)); deadbeef->plt_set_title (tab_clicked, text); - extern GtkWidget *mainwin; } gtk_widget_destroy (dlg); } @@ -659,31 +658,6 @@ on_add_new_playlist1_activate (GtkMenuItem *menuitem, } } -#if 0 -void -on_load_playlist1_activate (GtkMenuItem *menuitem, - gpointer user_data) -{ - -} - - -void -on_save_playlist1_activate (GtkMenuItem *menuitem, - gpointer user_data) -{ - -} - - -void -on_save_all_playlists1_activate (GtkMenuItem *menuitem, - gpointer user_data) -{ - -} -#endif - GtkWidget* create_plmenu (void) { @@ -720,73 +694,17 @@ create_plmenu (void) G_CALLBACK (on_add_new_playlist1_activate), NULL); - -#if 0 - separator11 = gtk_separator_menu_item_new (); - gtk_widget_show (separator11); - gtk_container_add (GTK_CONTAINER (plmenu), separator11); - gtk_widget_set_sensitive (separator11, FALSE); - - load_playlist1 = gtk_menu_item_new_with_mnemonic ("Load Playlist"); - gtk_widget_show (load_playlist1); - gtk_container_add (GTK_CONTAINER (plmenu), load_playlist1); - - save_playlist1 = gtk_menu_item_new_with_mnemonic ("Save Playlist"); - gtk_widget_show (save_playlist1); - gtk_container_add (GTK_CONTAINER (plmenu), save_playlist1); - - save_all_playlists1 = gtk_menu_item_new_with_mnemonic ("Save All Playlists"); - gtk_widget_show (save_all_playlists1); - gtk_container_add (GTK_CONTAINER (plmenu), save_all_playlists1); - - g_signal_connect ((gpointer) load_playlist1, "activate", - G_CALLBACK (on_load_playlist1_activate), - NULL); - g_signal_connect ((gpointer) save_playlist1, "activate", - G_CALLBACK (on_save_playlist1_activate), - NULL); - g_signal_connect ((gpointer) save_all_playlists1, "activate", - G_CALLBACK (on_save_all_playlists1_activate), - NULL); -#endif - /* Store pointers to all widgets, for use by lookup_widget(). */ GLADE_HOOKUP_OBJECT_NO_REF (plmenu, plmenu, "plmenu"); GLADE_HOOKUP_OBJECT (plmenu, rename_playlist1, "rename_playlist1"); GLADE_HOOKUP_OBJECT (plmenu, remove_playlist1, "remove_playlist1"); GLADE_HOOKUP_OBJECT (plmenu, add_new_playlist1, "add_new_playlist1"); -// GLADE_HOOKUP_OBJECT (plmenu, separator11, "separator11"); -// GLADE_HOOKUP_OBJECT (plmenu, load_playlist1, "load_playlist1"); -// GLADE_HOOKUP_OBJECT (plmenu, save_playlist1, "save_playlist1"); -// GLADE_HOOKUP_OBJECT (plmenu, save_all_playlists1, "save_all_playlists1"); return plmenu; } static void tabstrip_scroll_left (DdbTabStrip *ts) { -#if 0 - // scroll to leftmost border-spanning tab - int scrollsize = 0; - int w = 0; - int cnt = deadbeef->plt_get_count (); - for (int idx = 0; idx < cnt; idx++) { - int tab_w = ddb_tabstrip_get_tab_width (ts, idx); - if (w < ts->hscrollpos && w + tab_w >= ts->hscrollpos) { - scrollsize = ts->hscrollpos - w; - break; - } - w += tab_w - tab_overlap_size; - } - w += tab_overlap_size + 3; - - ts->hscrollpos -= scrollsize; - if (ts->hscrollpos < 0) { - ts->hscrollpos = 0; - } - deadbeef->conf_set_int ("gtkui.tabscroll", ts->hscrollpos); - gtk_widget_queue_draw (GTK_WIDGET (ts)); -#endif int tab = deadbeef->plt_get_curr (); if (tab > 0) { tab--; @@ -797,29 +715,6 @@ tabstrip_scroll_left (DdbTabStrip *ts) { static void tabstrip_scroll_right (DdbTabStrip *ts) { -#if 0 - // scroll to rightmost border-spanning tab - GtkWidget *widget = GTK_WIDGET (ts); - int scrollsize = 0; - int w = 0; - int cnt = deadbeef->plt_get_count (); - int boundary = widget->allocation.width - arrow_widget_width*2 + ts->hscrollpos; - for (int idx = 0; idx < cnt; idx++) { - int tab_w = ddb_tabstrip_get_tab_width (ts, idx); - - if (scrollsize == 0 && w < boundary && w + tab_w >= boundary) { - scrollsize = (w + tab_w) - boundary; - } - w += tab_w - tab_overlap_size; - } - w += tab_overlap_size + 3; - ts->hscrollpos += scrollsize; - if (ts->hscrollpos > w - (widget->allocation.width - arrow_widget_width*2)) { - ts->hscrollpos = w - (widget->allocation.width - arrow_widget_width*2); - } - deadbeef->conf_set_int ("gtkui.tabscroll", ts->hscrollpos); - gtk_widget_queue_draw (widget); -#endif int tab = deadbeef->plt_get_curr (); if (tab < deadbeef->plt_get_count ()-1) { tab++; @@ -920,7 +815,7 @@ on_tabstrip_button_press_event (GtkWidget *widget, } return FALSE; } - else if (deadbeef->conf_get_int ("gtkui.mmb_delete_playlist", 0)) { + else if (deadbeef->conf_get_int ("gtkui.mmb_delete_playlist", 1)) { if (tab_clicked != -1) { deadbeef->plt_remove (tab_clicked); int playlist = deadbeef->plt_get_curr (); diff --git a/plugins/gtkui/deadbeef.glade b/plugins/gtkui/deadbeef.glade index 08f73098..4f38af30 100644 --- a/plugins/gtkui/deadbeef.glade +++ b/plugins/gtkui/deadbeef.glade @@ -1252,76 +1252,14 @@ </child> <child> - <widget class="GtkButton" id="button1"> - <property name="width_request">83</property> + <widget class="GtkButton" id="button3"> <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label">gtk-cancel</property> + <property name="use_stock">True</property> <property name="relief">GTK_RELIEF_NORMAL</property> <property name="focus_on_click">True</property> - <signal name="clicked" handler="on_progress_abort" last_modification_time="Sun, 16 Aug 2009 17:17:12 GMT"/> - - <child> - <widget class="GtkAlignment" id="alignment10"> - <property name="visible">True</property> - <property name="xalign">0.5</property> - <property name="yalign">0.5</property> - <property name="xscale">0</property> - <property name="yscale">0</property> - <property name="top_padding">0</property> - <property name="bottom_padding">0</property> - <property name="left_padding">0</property> - <property name="right_padding">0</property> - - <child> - <widget class="GtkHBox" id="hbox51"> - <property name="visible">True</property> - <property name="homogeneous">False</property> - <property name="spacing">2</property> - - <child> - <widget class="GtkImage" id="image389"> - <property name="visible">True</property> - <property name="stock">gtk-stop</property> - <property name="icon_size">4</property> - <property name="xalign">0.5</property> - <property name="yalign">0.5</property> - <property name="xpad">0</property> - <property name="ypad">0</property> - </widget> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - - <child> - <widget class="GtkLabel" id="label87"> - <property name="visible">True</property> - <property name="label" translatable="yes">_Abort</property> - <property name="use_underline">True</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_LEFT</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">0.5</property> - <property name="yalign">0.5</property> - <property name="xpad">0</property> - <property name="ypad">0</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - </widget> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - </widget> - </child> - </widget> - </child> + <signal name="clicked" handler="on_progress_abort" last_modification_time="Mon, 25 Oct 2010 20:04:28 GMT"/> </widget> <packing> <property name="padding">0</property> @@ -3063,7 +3001,7 @@ Album</property> <property name="show_tabs">True</property> <property name="show_border">True</property> <property name="tab_pos">GTK_POS_TOP</property> - <property name="scrollable">False</property> + <property name="scrollable">True</property> <property name="enable_popup">False</property> <child> diff --git a/plugins/gtkui/gtkui.c b/plugins/gtkui/gtkui.c index 2dd40a73..1c2b9b9f 100644 --- a/plugins/gtkui/gtkui.c +++ b/plugins/gtkui/gtkui.c @@ -778,22 +778,23 @@ on_add_location_activate (GtkMenuItem *menuitem, static void songchanged (DdbListview *ps, DB_playItem_t *from, DB_playItem_t *to) { int plt = deadbeef->plt_get_curr (); -#if 0 // this breaks redraw when playqueue switches to another playlist - int str_plt = deadbeef->streamer_get_current_playlist (); - if (plt != str_plt) { - // have nothing to do here -- active playlist is not the one with playing song - return; - } -#endif int to_idx = -1; if (!ddb_listview_is_scrolling (ps) && to) { - to_idx = deadbeef->pl_get_idx_of (to); - if (to_idx != -1) { - if (deadbeef->conf_get_int ("playlist.scroll.followplayback", 0)) { - ddb_listview_scroll_to (ps, to_idx); + int cursor_follows_playback = deadbeef->conf_get_int ("playlist.scroll.cursorfollowplayback", 0); + int scroll_follows_playback = deadbeef->conf_get_int ("playlist.scroll.followplayback", 0); + int plt = deadbeef->streamer_get_current_playlist (); + if (plt != -1) { + if (cursor_follows_playback && plt != deadbeef->plt_get_curr ()) { + deadbeef->plt_set_curr (plt); } - if (deadbeef->conf_get_int ("playlist.scroll.cursorfollowplayback", 0)) { - ddb_listview_set_cursor_noscroll (ps, to_idx); + to_idx = deadbeef->pl_get_idx_of (to); + if (to_idx != -1) { + if (cursor_follows_playback) { + ddb_listview_set_cursor_noscroll (ps, to_idx); + } + if (scroll_follows_playback && plt == deadbeef->plt_get_curr ()) { + ddb_listview_scroll_to (ps, to_idx); + } } } } @@ -1071,9 +1072,16 @@ void gtkui_focus_on_playing_track (void) { DB_playItem_t *it = deadbeef->streamer_get_playing_track (); if (it) { + int plt = deadbeef->streamer_get_current_playlist (); + if (plt != deadbeef->plt_get_curr ()) { + deadbeef->plt_set_curr (plt); + } int idx = deadbeef->pl_get_idx_of (it); - ddb_listview_scroll_to (DDB_LISTVIEW (lookup_widget (mainwin, "playlist")), idx); - ddb_listview_set_cursor (DDB_LISTVIEW (lookup_widget (mainwin, "playlist")), idx); + if (idx != -1) { + DdbListview *pl = DDB_LISTVIEW (lookup_widget (mainwin, "playlist")); + ddb_listview_scroll_to (pl, idx); + ddb_listview_set_cursor (pl, idx); + } deadbeef->pl_item_unref (it); } } diff --git a/plugins/gtkui/interface.c b/plugins/gtkui/interface.c index 6ee99477..1b255c52 100644 --- a/plugins/gtkui/interface.c +++ b/plugins/gtkui/interface.c @@ -1034,11 +1034,7 @@ create_addprogress (void) GtkWidget *progresstitle; GtkWidget *hbox7; GtkWidget *label22; - GtkWidget *button1; - GtkWidget *alignment10; - GtkWidget *hbox51; - GtkWidget *image389; - GtkWidget *label87; + GtkWidget *button3; addprogress = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_container_set_border_width (GTK_CONTAINER (addprogress), 12); @@ -1068,32 +1064,14 @@ create_addprogress (void) gtk_widget_show (label22); gtk_box_pack_start (GTK_BOX (hbox7), label22, TRUE, FALSE, 0); - button1 = gtk_button_new (); - gtk_widget_show (button1); - gtk_box_pack_start (GTK_BOX (hbox7), button1, FALSE, FALSE, 0); - gtk_widget_set_size_request (button1, 83, -1); - GTK_WIDGET_UNSET_FLAGS (button1, GTK_CAN_FOCUS); - - alignment10 = gtk_alignment_new (0.5, 0.5, 0, 0); - gtk_widget_show (alignment10); - gtk_container_add (GTK_CONTAINER (button1), alignment10); - - hbox51 = gtk_hbox_new (FALSE, 2); - gtk_widget_show (hbox51); - gtk_container_add (GTK_CONTAINER (alignment10), hbox51); - - image389 = gtk_image_new_from_stock ("gtk-stop", GTK_ICON_SIZE_BUTTON); - gtk_widget_show (image389); - gtk_box_pack_start (GTK_BOX (hbox51), image389, FALSE, FALSE, 0); - - label87 = gtk_label_new_with_mnemonic (_("_Abort")); - gtk_widget_show (label87); - gtk_box_pack_start (GTK_BOX (hbox51), label87, FALSE, FALSE, 0); + button3 = gtk_button_new_from_stock ("gtk-cancel"); + gtk_widget_show (button3); + gtk_box_pack_start (GTK_BOX (hbox7), button3, FALSE, FALSE, 0); g_signal_connect ((gpointer) addprogress, "delete_event", G_CALLBACK (on_addprogress_delete_event), NULL); - g_signal_connect ((gpointer) button1, "clicked", + g_signal_connect ((gpointer) button3, "clicked", G_CALLBACK (on_progress_abort), NULL); @@ -1103,11 +1081,7 @@ create_addprogress (void) GLADE_HOOKUP_OBJECT (addprogress, progresstitle, "progresstitle"); GLADE_HOOKUP_OBJECT (addprogress, hbox7, "hbox7"); GLADE_HOOKUP_OBJECT (addprogress, label22, "label22"); - GLADE_HOOKUP_OBJECT (addprogress, button1, "button1"); - GLADE_HOOKUP_OBJECT (addprogress, alignment10, "alignment10"); - GLADE_HOOKUP_OBJECT (addprogress, hbox51, "hbox51"); - GLADE_HOOKUP_OBJECT (addprogress, image389, "image389"); - GLADE_HOOKUP_OBJECT (addprogress, label87, "label87"); + GLADE_HOOKUP_OBJECT (addprogress, button3, "button3"); return addprogress; } @@ -1908,6 +1882,7 @@ create_prefwin (void) gtk_widget_show (notebook4); gtk_container_add (GTK_CONTAINER (notebook), notebook4); gtk_container_set_border_width (GTK_CONTAINER (notebook4), 12); + gtk_notebook_set_scrollable (GTK_NOTEBOOK (notebook4), TRUE); vbox21 = gtk_vbox_new (FALSE, 8); gtk_widget_show (vbox21); diff --git a/plugins/gtkui/prefwin.c b/plugins/gtkui/prefwin.c index c2b8a63a..4e1c42b0 100644 --- a/plugins/gtkui/prefwin.c +++ b/plugins/gtkui/prefwin.c @@ -521,7 +521,7 @@ on_preferences_activate (GtkMenuItem *menuitem, gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (lookup_widget (w, "hide_tray_icon")), deadbeef->conf_get_int ("gtkui.hide_tray_icon", 0)); // mmb_delete_playlist - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (lookup_widget (w, "mmb_delete_playlist")), deadbeef->conf_get_int ("gtkui.mmb_delete_playlist", 0)); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (lookup_widget (w, "mmb_delete_playlist")), deadbeef->conf_get_int ("gtkui.mmb_delete_playlist", 1)); // embolden current track gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (lookup_widget (w, "embolden_current")), deadbeef->conf_get_int ("gtkui.embolden_current_track", 0)); diff --git a/plugins/lastfm/lastfm.c b/plugins/lastfm/lastfm.c index 27c6f6f0..4a234e8f 100644 --- a/plugins/lastfm/lastfm.c +++ b/plugins/lastfm/lastfm.c @@ -119,6 +119,7 @@ curl_req_send (const char *req, const char *post) { memset(lfm_err, 0, sizeof(lfm_err)); curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, lfm_err); curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + curl_easy_setopt (curl, CURLOPT_NOSIGNAL, 1); curl_easy_setopt (curl, CURLOPT_PROGRESSFUNCTION, lfm_curl_control); curl_easy_setopt (curl, CURLOPT_NOPROGRESS, 0); if (post) { diff --git a/plugins/mms/Makefile.am b/plugins/mms/Makefile.am index c6a06fdb..f16f7412 100644 --- a/plugins/mms/Makefile.am +++ b/plugins/mms/Makefile.am @@ -1,7 +1,20 @@ if HAVE_MMS mmsdir = $(libdir)/$(PACKAGE) pkglib_LTLIBRARIES = mms.la -mms_la_SOURCES = mms.c +mms_la_SOURCES = mmsplug.c\ + libmms/mms.c\ + libmms/mmsh.c\ + libmms/mmsx.c\ + libmms/uri.c\ + libmms/asfheader.h\ + libmms/bswap.h\ + libmms/mms-common.h\ + libmms/mms.h\ + libmms/mmsh.h\ + libmms/mmsio.h\ + libmms/mmsx.h\ + libmms/uri.h + mms_la_LDFLAGS = -module mms_la_LIBADD = $(LDADD) $(LIBMMS_LIBS) diff --git a/plugins/mms/libmms/AUTHORS b/plugins/mms/libmms/AUTHORS new file mode 100644 index 00000000..04a68431 --- /dev/null +++ b/plugins/mms/libmms/AUTHORS @@ -0,0 +1,6 @@ +Original author of the MMS interface code was Major MMS of http://www.geocities.com/majormms/ +Enhanced and maintained by Xine project at http://xine.sf.net + +Current developers of libmms are: +Maciej Katafiasz (Mathrick) <mathrick@users.sourceforge.net> +Søren Hansen (shawarma) <sh@warma.dk> diff --git a/plugins/mms/libmms/COPYING.LIB b/plugins/mms/libmms/COPYING.LIB new file mode 100644 index 00000000..b124cf58 --- /dev/null +++ b/plugins/mms/libmms/COPYING.LIB @@ -0,0 +1,510 @@ + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations +below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it +becomes a de-facto standard. To achieve this, non-free programs must +be allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control +compilation and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at least + three years, to give the same user the materials specified in + Subsection 6a, above, for a charge no more than the cost of + performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply, and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License +may add an explicit geographical distribution limitation excluding those +countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms +of the ordinary General Public License). + + To apply these terms, attach the following notices to the library. +It is safest to attach them to the start of each source file to most +effectively convey the exclusion of warranty; and each file should +have at least the "copyright" line and a pointer to where the full +notice is found. + + + <one line to give the library's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or +your school, if any, to sign a "copyright disclaimer" for the library, +if necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James + Random Hacker. + + <signature of Ty Coon>, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/plugins/mms/libmms/README b/plugins/mms/libmms/README new file mode 100644 index 00000000..8426d2cf --- /dev/null +++ b/plugins/mms/libmms/README @@ -0,0 +1,57 @@ + LibMMS + + library for parsing + Microsoft Media Streaming + protocol + + +What is LibMMS? +=============== + +LibMMS is common library for parsing mms:// and mmsh:// type network +streams. These are commonly used to stream Windows Media Video content +over the web. LibMMS itself is only for receiving MMS stream, it +doesn't handle sending at all. If you need streaming functionality, +look for other protocol, such as RT(S)P. MMS is legacy thing, being +closed with no specs and abandoned even by its creator, the Microsoft Corp. + +Why LibMMS? +=========== + +LibMMS was created out of need for common library that would remedy +current situation where each Free Software project maintains its own +implementation of MMS protocol handling, creating unnecessary code and +effort duplication and introducing feature disparity as not every +bugfix gets into each of them. It also addresses need for LGPL +licensed code for that task, as every current implementation is +licensed as GPL, thus knocking out many projects that do not use GPL +themselves. + +Howto LibMMS? +============= + +LibMMS is intended to be small and simple, being useful for any +project that needs MMS handling functionality. It is constructed in +such a way that allows plugging custom I/O implementation, thus being +easy to integrate with custom framework, and providing a way to add +timeouted or cancelable I/O etc. + +Hmm, you said no specs? How so? +=============================== + +LibMMS code is based on amazing work done by SDP guys (http://get.to/sdp) +Without the specs they've reverse-engineered, there won't be any free +implementation of MMS handling today. + +How is LibMMS licensed? +======================= + +LibMMS is Free Software, licensed under GNU Library General Public +License. Original code comes from Xine project (http://xine.sf.net), +and it got separated for number of reasons, one of them being desire +to provide non GPL projects with library they could use. There's a +number of valuable LGPL projects locked out by GPL libraries, besides +we strongly feel that GPL is bad license for a library, as it's not +library thing to mess with its users licensing by vague interpretations +of wether linking makes binary derived work. We want libraries to be +used, and for that, we need to stay clean.
\ No newline at end of file diff --git a/plugins/mms/libmms/README.LICENSE b/plugins/mms/libmms/README.LICENSE new file mode 100644 index 00000000..15dfbd25 --- /dev/null +++ b/plugins/mms/libmms/README.LICENSE @@ -0,0 +1,6 @@ +Original GPL code was taken from Xine project +(http://xine.sf.net). Relicensed to LGPL with explicit approval from +all copyright holders, if you're interested, you can see thread at: + +<FIXME: add link to xine's mailing archives, import mails from my +local mailbox>
\ No newline at end of file diff --git a/plugins/mms/libmms/asfheader.h b/plugins/mms/libmms/asfheader.h new file mode 100644 index 00000000..2aaffb39 --- /dev/null +++ b/plugins/mms/libmms/asfheader.h @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2000-2003 the xine project + * + * This file is part of xine, a free video player. + * + * xine is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * xine is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + * $Id: asfheader.h,v 1.2 2005/12/12 09:24:19 theuraeus Exp $ + * + * demultiplexer for asf streams + * + * based on ffmpeg's + * ASF compatible encoder and decoder. + * Copyright (c) 2000, 2001 Gerard Lantau. + * + * GUID list from avifile + * some other ideas from MPlayer + */ + +#ifndef ASFHEADER_H +#define ASFHEADER_H + +/* + * define asf GUIDs (list from avifile) + */ +#define GUID_ERROR 0 + + /* base ASF objects */ +#define GUID_ASF_HEADER 1 +#define GUID_ASF_DATA 2 +#define GUID_ASF_SIMPLE_INDEX 3 +#define GUID_INDEX 4 +#define GUID_MEDIA_OBJECT_INDEX 5 +#define GUID_TIMECODE_INDEX 6 + + /* header ASF objects */ +#define GUID_ASF_FILE_PROPERTIES 7 +#define GUID_ASF_STREAM_PROPERTIES 8 +#define GUID_ASF_HEADER_EXTENSION 9 +#define GUID_ASF_CODEC_LIST 10 +#define GUID_ASF_SCRIPT_COMMAND 11 +#define GUID_ASF_MARKER 12 +#define GUID_ASF_BITRATE_MUTUAL_EXCLUSION 13 +#define GUID_ASF_ERROR_CORRECTION 14 +#define GUID_ASF_CONTENT_DESCRIPTION 15 +#define GUID_ASF_EXTENDED_CONTENT_DESCRIPTION 16 +#define GUID_ASF_STREAM_BITRATE_PROPERTIES 17 +#define GUID_ASF_EXTENDED_CONTENT_ENCRYPTION 18 +#define GUID_ASF_PADDING 19 + + /* stream properties object stream type */ +#define GUID_ASF_AUDIO_MEDIA 20 +#define GUID_ASF_VIDEO_MEDIA 21 +#define GUID_ASF_COMMAND_MEDIA 22 +#define GUID_ASF_JFIF_MEDIA 23 +#define GUID_ASF_DEGRADABLE_JPEG_MEDIA 24 +#define GUID_ASF_FILE_TRANSFER_MEDIA 25 +#define GUID_ASF_BINARY_MEDIA 26 + + /* stream properties object error correction type */ +#define GUID_ASF_NO_ERROR_CORRECTION 27 +#define GUID_ASF_AUDIO_SPREAD 28 + + /* mutual exclusion object exlusion type */ +#define GUID_ASF_MUTEX_BITRATE 29 +#define GUID_ASF_MUTEX_UKNOWN 30 + + /* header extension */ +#define GUID_ASF_RESERVED_1 31 + + /* script command */ +#define GUID_ASF_RESERVED_SCRIPT_COMMNAND 32 + + /* marker object */ +#define GUID_ASF_RESERVED_MARKER 33 + + /* various */ +/* +#define GUID_ASF_HEAD2 27 +*/ +#define GUID_ASF_AUDIO_CONCEAL_NONE 34 +#define GUID_ASF_CODEC_COMMENT1_HEADER 35 +#define GUID_ASF_2_0_HEADER 36 +#define GUID_ASF_EXTENDED_STREAM_PROPERTIES 37 + +#define GUID_END 38 + + +/* asf stream types */ +#define ASF_STREAM_TYPE_UNKNOWN 0 +#define ASF_STREAM_TYPE_AUDIO 1 +#define ASF_STREAM_TYPE_VIDEO 2 +#define ASF_STREAM_TYPE_CONTROL 3 +#define ASF_STREAM_TYPE_JFIF 4 +#define ASF_STREAM_TYPE_DEGRADABLE_JPEG 5 +#define ASF_STREAM_TYPE_FILE_TRANSFER 6 +#define ASF_STREAM_TYPE_BINARY 7 + +#define ASF_MAX_NUM_STREAMS 23 + +#ifndef GUID_DEFINED +#define GUID_DEFINED + +typedef struct _GUID { /* size is 16 */ + uint32_t Data1; + uint16_t Data2; + uint16_t Data3; + uint8_t Data4[8]; +} GUID; + +#endif /* !GUID_DEFINED */ + +static const struct +{ + const char* name; + const GUID guid; +} guids[] = +{ + { "error", + { 0x0,} }, + + + /* base ASF objects */ + { "header", + { 0x75b22630, 0x668e, 0x11cf, { 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c }} }, + + { "data", + { 0x75b22636, 0x668e, 0x11cf, { 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c }} }, + + { "simple index", + { 0x33000890, 0xe5b1, 0x11cf, { 0x89, 0xf4, 0x00, 0xa0, 0xc9, 0x03, 0x49, 0xcb }} }, + + { "index", + { 0xd6e229d3, 0x35da, 0x11d1, { 0x90, 0x34, 0x00, 0xa0, 0xc9, 0x03, 0x49, 0xbe }} }, + + { "media object index", + { 0xfeb103f8, 0x12ad, 0x4c64, { 0x84, 0x0f, 0x2a, 0x1d, 0x2f, 0x7a, 0xd4, 0x8c }} }, + + { "timecode index", + { 0x3cb73fd0, 0x0c4a, 0x4803, { 0x95, 0x3d, 0xed, 0xf7, 0xb6, 0x22, 0x8f, 0x0c }} }, + + /* header ASF objects */ + { "file properties", + { 0x8cabdca1, 0xa947, 0x11cf, { 0x8e, 0xe4, 0x00, 0xc0, 0x0c, 0x20, 0x53, 0x65 }} }, + + { "stream header", + { 0xb7dc0791, 0xa9b7, 0x11cf, { 0x8e, 0xe6, 0x00, 0xc0, 0x0c, 0x20, 0x53, 0x65 }} }, + + { "header extension", + { 0x5fbf03b5, 0xa92e, 0x11cf, { 0x8e, 0xe3, 0x00, 0xc0, 0x0c, 0x20, 0x53, 0x65 }} }, + + { "codec list", + { 0x86d15240, 0x311d, 0x11d0, { 0xa3, 0xa4, 0x00, 0xa0, 0xc9, 0x03, 0x48, 0xf6 }} }, + + { "script command", + { 0x1efb1a30, 0x0b62, 0x11d0, { 0xa3, 0x9b, 0x00, 0xa0, 0xc9, 0x03, 0x48, 0xf6 }} }, + + { "marker", + { 0xf487cd01, 0xa951, 0x11cf, { 0x8e, 0xe6, 0x00, 0xc0, 0x0c, 0x20, 0x53, 0x65 }} }, + + { "bitrate mutual exclusion", + { 0xd6e229dc, 0x35da, 0x11d1, { 0x90, 0x34, 0x00, 0xa0, 0xc9, 0x03, 0x49, 0xbe }} }, + + { "error correction", + { 0x75b22635, 0x668e, 0x11cf, { 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c }} }, + + { "content description", + { 0x75b22633, 0x668e, 0x11cf, { 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c }} }, + + { "extended content description", + { 0xd2d0a440, 0xe307, 0x11d2, { 0x97, 0xf0, 0x00, 0xa0, 0xc9, 0x5e, 0xa8, 0x50 }} }, + + { "stream bitrate properties", /* (http://get.to/sdp) */ + { 0x7bf875ce, 0x468d, 0x11d1, { 0x8d, 0x82, 0x00, 0x60, 0x97, 0xc9, 0xa2, 0xb2 }} }, + + { "extended content encryption", + { 0x298ae614, 0x2622, 0x4c17, { 0xb9, 0x35, 0xda, 0xe0, 0x7e, 0xe9, 0x28, 0x9c }} }, + + { "padding", + { 0x1806d474, 0xcadf, 0x4509, { 0xa4, 0xba, 0x9a, 0xab, 0xcb, 0x96, 0xaa, 0xe8 }} }, + + + /* stream properties object stream type */ + { "audio media", + { 0xf8699e40, 0x5b4d, 0x11cf, { 0xa8, 0xfd, 0x00, 0x80, 0x5f, 0x5c, 0x44, 0x2b }} }, + + { "video media", + { 0xbc19efc0, 0x5b4d, 0x11cf, { 0xa8, 0xfd, 0x00, 0x80, 0x5f, 0x5c, 0x44, 0x2b }} }, + + { "command media", + { 0x59dacfc0, 0x59e6, 0x11d0, { 0xa3, 0xac, 0x00, 0xa0, 0xc9, 0x03, 0x48, 0xf6 }} }, + + { "JFIF media (JPEG)", + { 0xb61be100, 0x5b4e, 0x11cf, { 0xa8, 0xfd, 0x00, 0x80, 0x5f, 0x5c, 0x44, 0x2b }} }, + + { "Degradable JPEG media", + { 0x35907de0, 0xe415, 0x11cf, { 0xa9, 0x17, 0x00, 0x80, 0x5f, 0x5c, 0x44, 0x2b }} }, + + { "File Transfer media", + { 0x91bd222c, 0xf21c, 0x497a, { 0x8b, 0x6d, 0x5a, 0xa8, 0x6b, 0xfc, 0x01, 0x85 }} }, + + { "Binary media", + { 0x3afb65e2, 0x47ef, 0x40f2, { 0xac, 0x2c, 0x70, 0xa9, 0x0d, 0x71, 0xd3, 0x43 }} }, + + /* stream properties object error correction */ + { "no error correction", + { 0x20fb5700, 0x5b55, 0x11cf, { 0xa8, 0xfd, 0x00, 0x80, 0x5f, 0x5c, 0x44, 0x2b }} }, + + { "audio spread", + { 0xbfc3cd50, 0x618f, 0x11cf, { 0x8b, 0xb2, 0x00, 0xaa, 0x00, 0xb4, 0xe2, 0x20 }} }, + + + /* mutual exclusion object exlusion type */ + { "mutex bitrate", + { 0xd6e22a01, 0x35da, 0x11d1, { 0x90, 0x34, 0x00, 0xa0, 0xc9, 0x03, 0x49, 0xbe }} }, + + { "mutex unknown", + { 0xd6e22a02, 0x35da, 0x11d1, { 0x90, 0x34, 0x00, 0xa0, 0xc9, 0x03, 0x49, 0xbe }} }, + + + /* header extension */ + { "reserved_1", + { 0xabd3d211, 0xa9ba, 0x11cf, { 0x8e, 0xe6, 0x00, 0xc0, 0x0c, 0x20, 0x53, 0x65 }} }, + + + /* script command */ + { "reserved script command", + { 0x4B1ACBE3, 0x100B, 0x11D0, { 0xA3, 0x9B, 0x00, 0xA0, 0xC9, 0x03, 0x48, 0xF6 }} }, + + /* marker object */ + { "reserved marker", + { 0x4CFEDB20, 0x75F6, 0x11CF, { 0x9C, 0x0F, 0x00, 0xA0, 0xC9, 0x03, 0x49, 0xCB }} }, + + /* various */ + /* Already defined (reserved_1) + { "head2", + { 0xabd3d211, 0xa9ba, 0x11cf, { 0x8e, 0xe6, 0x00, 0xc0, 0x0c, 0x20, 0x53, 0x65 }} }, + */ + { "audio conceal none", + { 0x49f1a440, 0x4ece, 0x11d0, { 0xa3, 0xac, 0x00, 0xa0, 0xc9, 0x03, 0x48, 0xf6 }} }, + + { "codec comment1 header", + { 0x86d15241, 0x311d, 0x11d0, { 0xa3, 0xa4, 0x00, 0xa0, 0xc9, 0x03, 0x48, 0xf6 }} }, + + { "asf 2.0 header", + { 0xd6e229d1, 0x35da, 0x11d1, { 0x90, 0x34, 0x00, 0xa0, 0xc9, 0x03, 0x49, 0xbe }} }, + + { "extended stream properties", + { 0x14e6a5cb, 0xc672, 0x4332, { 0x83, 0x99, 0xa9, 0x69, 0x52, 0x06, 0x5b, 0x5a }} }, + +}; + +#endif diff --git a/plugins/mms/libmms/bswap.h b/plugins/mms/libmms/bswap.h new file mode 100644 index 00000000..6a6e1d10 --- /dev/null +++ b/plugins/mms/libmms/bswap.h @@ -0,0 +1,282 @@ +#ifndef BSWAP_H_INCLUDED +#define BSWAP_H_INCLUDED + +/* + * Copyright (C) 2004 Maciej Katafiasz <mathrick@users.sourceforge.net> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +/* NOTE: + * Now, to clear up confusion: LE_XX means "from LE to native, XX bits wide" + * I know it's not very clear naming (tell me about it, I + * misinterpreted in first version and caused bad nasty bug, *sigh*), + * but that's inherited code, will clean up as things go + * Oh, and one more thing -- they take *pointers*, not actual ints + */ + +/* Basic bit swapping functions + */ +#define GUINT16_SWAP_LE_BE_CONSTANT(val) ((guint16) ( \ + (guint16) ((guint16) (val) >> 8) | \ + (guint16) ((guint16) (val) << 8))) + +#define GUINT32_SWAP_LE_BE_CONSTANT(val) ((guint32) ( \ + (((guint32) (val) & (guint32) 0x000000ffU) << 24) | \ + (((guint32) (val) & (guint32) 0x0000ff00U) << 8) | \ + (((guint32) (val) & (guint32) 0x00ff0000U) >> 8) | \ + (((guint32) (val) & (guint32) 0xff000000U) >> 24))) + +#define GUINT64_SWAP_LE_BE_CONSTANT(val) ((guint64) ( \ + (((guint64) (val) & \ + (guint64) G_GINT64_CONSTANT (0x00000000000000ffU)) << 56) | \ + (((guint64) (val) & \ + (guint64) G_GINT64_CONSTANT (0x000000000000ff00U)) << 40) | \ + (((guint64) (val) & \ + (guint64) G_GINT64_CONSTANT (0x0000000000ff0000U)) << 24) | \ + (((guint64) (val) & \ + (guint64) G_GINT64_CONSTANT (0x00000000ff000000U)) << 8) | \ + (((guint64) (val) & \ + (guint64) G_GINT64_CONSTANT (0x000000ff00000000U)) >> 8) | \ + (((guint64) (val) & \ + (guint64) G_GINT64_CONSTANT (0x0000ff0000000000U)) >> 24) | \ + (((guint64) (val) & \ + (guint64) G_GINT64_CONSTANT (0x00ff000000000000U)) >> 40) | \ + (((guint64) (val) & \ + (guint64) G_GINT64_CONSTANT (0xff00000000000000U)) >> 56))) + +/* Arch specific stuff for speed + */ +#if defined (__GNUC__) && (__GNUC__ >= 2) && defined (__OPTIMIZE__) +# if defined (__i386__) +# define GUINT16_SWAP_LE_BE_IA32(val) \ + (__extension__ \ + ({ register guint16 __v, __x = ((guint16) (val)); \ + if (__builtin_constant_p (__x)) \ + __v = GUINT16_SWAP_LE_BE_CONSTANT (__x); \ + else \ + __asm__ ("rorw $8, %w0" \ + : "=r" (__v) \ + : "0" (__x) \ + : "cc"); \ + __v; })) +# if !defined (__i486__) && !defined (__i586__) \ + && !defined (__pentium__) && !defined (__i686__) \ + && !defined (__pentiumpro__) && !defined (__pentium4__) +# define GUINT32_SWAP_LE_BE_IA32(val) \ + (__extension__ \ + ({ register guint32 __v, __x = ((guint32) (val)); \ + if (__builtin_constant_p (__x)) \ + __v = GUINT32_SWAP_LE_BE_CONSTANT (__x); \ + else \ + __asm__ ("rorw $8, %w0\n\t" \ + "rorl $16, %0\n\t" \ + "rorw $8, %w0" \ + : "=r" (__v) \ + : "0" (__x) \ + : "cc"); \ + __v; })) +# else /* 486 and higher has bswap */ +# define GUINT32_SWAP_LE_BE_IA32(val) \ + (__extension__ \ + ({ register guint32 __v, __x = ((guint32) (val)); \ + if (__builtin_constant_p (__x)) \ + __v = GUINT32_SWAP_LE_BE_CONSTANT (__x); \ + else \ + __asm__ ("bswap %0" \ + : "=r" (__v) \ + : "0" (__x)); \ + __v; })) +# endif /* processor specific 32-bit stuff */ +# define GUINT64_SWAP_LE_BE_IA32(val) \ + (__extension__ \ + ({ union { guint64 __ll; \ + guint32 __l[2]; } __w, __r; \ + __w.__ll = ((guint64) (val)); \ + if (__builtin_constant_p (__w.__ll)) \ + __r.__ll = GUINT64_SWAP_LE_BE_CONSTANT (__w.__ll); \ + else \ + { \ + __r.__l[0] = GUINT32_SWAP_LE_BE (__w.__l[1]); \ + __r.__l[1] = GUINT32_SWAP_LE_BE (__w.__l[0]); \ + } \ + __r.__ll; })) + /* Possibly just use the constant version and let gcc figure it out? */ +# define GUINT16_SWAP_LE_BE(val) (GUINT16_SWAP_LE_BE_IA32 (val)) +# define GUINT32_SWAP_LE_BE(val) (GUINT32_SWAP_LE_BE_IA32 (val)) +# define GUINT64_SWAP_LE_BE(val) (GUINT64_SWAP_LE_BE_IA32 (val)) +# elif defined (__ia64__) +# define GUINT16_SWAP_LE_BE_IA64(val) \ + (__extension__ \ + ({ register guint16 __v, __x = ((guint16) (val)); \ + if (__builtin_constant_p (__x)) \ + __v = GUINT16_SWAP_LE_BE_CONSTANT (__x); \ + else \ + __asm__ __volatile__ ("shl %0 = %1, 48 ;;" \ + "mux1 %0 = %0, @rev ;;" \ + : "=r" (__v) \ + : "r" (__x)); \ + __v; })) +# define GUINT32_SWAP_LE_BE_IA64(val) \ + (__extension__ \ + ({ register guint32 __v, __x = ((guint32) (val)); \ + if (__builtin_constant_p (__x)) \ + __v = GUINT32_SWAP_LE_BE_CONSTANT (__x); \ + else \ + __asm__ __volatile__ ("shl %0 = %1, 32 ;;" \ + "mux1 %0 = %0, @rev ;;" \ + : "=r" (__v) \ + : "r" (__x)); \ + __v; })) +# define GUINT64_SWAP_LE_BE_IA64(val) \ + (__extension__ \ + ({ register guint64 __v, __x = ((guint64) (val)); \ + if (__builtin_constant_p (__x)) \ + __v = GUINT64_SWAP_LE_BE_CONSTANT (__x); \ + else \ + __asm__ __volatile__ ("mux1 %0 = %1, @rev ;;" \ + : "=r" (__v) \ + : "r" (__x)); \ + __v; })) +# define GUINT16_SWAP_LE_BE(val) (GUINT16_SWAP_LE_BE_IA64 (val)) +# define GUINT32_SWAP_LE_BE(val) (GUINT32_SWAP_LE_BE_IA64 (val)) +# define GUINT64_SWAP_LE_BE(val) (GUINT64_SWAP_LE_BE_IA64 (val)) +# elif defined (__x86_64__) +# define GUINT32_SWAP_LE_BE_X86_64(val) \ + (__extension__ \ + ({ register guint32 __v, __x = ((guint32) (val)); \ + if (__builtin_constant_p (__x)) \ + __v = GUINT32_SWAP_LE_BE_CONSTANT (__x); \ + else \ + __asm__ ("bswapl %0" \ + : "=r" (__v) \ + : "0" (__x)); \ + __v; })) +# define GUINT64_SWAP_LE_BE_X86_64(val) \ + (__extension__ \ + ({ register guint64 __v, __x = ((guint64) (val)); \ + if (__builtin_constant_p (__x)) \ + __v = GUINT64_SWAP_LE_BE_CONSTANT (__x); \ + else \ + __asm__ ("bswapq %0" \ + : "=r" (__v) \ + : "0" (__x)); \ + __v; })) + /* gcc seems to figure out optimal code for this on its own */ +# define GUINT16_SWAP_LE_BE(val) (GUINT16_SWAP_LE_BE_CONSTANT (val)) +# define GUINT32_SWAP_LE_BE(val) (GUINT32_SWAP_LE_BE_X86_64 (val)) +# define GUINT64_SWAP_LE_BE(val) (GUINT64_SWAP_LE_BE_X86_64 (val)) +# else /* generic gcc */ +# define GUINT16_SWAP_LE_BE(val) (GUINT16_SWAP_LE_BE_CONSTANT (val)) +# define GUINT32_SWAP_LE_BE(val) (GUINT32_SWAP_LE_BE_CONSTANT (val)) +# define GUINT64_SWAP_LE_BE(val) (GUINT64_SWAP_LE_BE_CONSTANT (val)) +# endif +#else /* generic */ +# define GUINT16_SWAP_LE_BE(val) (GUINT16_SWAP_LE_BE_CONSTANT (val)) +# define GUINT32_SWAP_LE_BE(val) (GUINT32_SWAP_LE_BE_CONSTANT (val)) +# define GUINT64_SWAP_LE_BE(val) (GUINT64_SWAP_LE_BE_CONSTANT (val)) +#endif /* generic */ + +#define GUINT16_SWAP_LE_PDP(val) ((guint16) (val)) +#define GUINT16_SWAP_BE_PDP(val) (GUINT16_SWAP_LE_BE (val)) +#define GUINT32_SWAP_LE_PDP(val) ((guint32) ( \ + (((guint32) (val) & (guint32) 0x0000ffffU) << 16) | \ + (((guint32) (val) & (guint32) 0xffff0000U) >> 16))) +#define GUINT32_SWAP_BE_PDP(val) ((guint32) ( \ + (((guint32) (val) & (guint32) 0x00ff00ffU) << 8) | \ + (((guint32) (val) & (guint32) 0xff00ff00U) >> 8))) + + +/* The G*_TO_?E() macros are defined in glibconfig.h. + * The transformation is symmetric, so the FROM just maps to the TO. + */ +#define GINT16_FROM_LE(val) (GINT16_TO_LE (val)) +#define GUINT16_FROM_LE(val) (GUINT16_TO_LE (val)) +#define GINT16_FROM_BE(val) (GINT16_TO_BE (val)) +#define GUINT16_FROM_BE(val) (GUINT16_TO_BE (val)) +#define GINT32_FROM_LE(val) (GINT32_TO_LE (val)) +#define GUINT32_FROM_LE(val) (GUINT32_TO_LE (val)) +#define GINT32_FROM_BE(val) (GINT32_TO_BE (val)) +#define GUINT32_FROM_BE(val) (GUINT32_TO_BE (val)) + +#define GINT64_FROM_LE(val) (GINT64_TO_LE (val)) +#define GUINT64_FROM_LE(val) (GUINT64_TO_LE (val)) +#define GINT64_FROM_BE(val) (GINT64_TO_BE (val)) +#define GUINT64_FROM_BE(val) (GUINT64_TO_BE (val)) + +#define GLONG_FROM_LE(val) (GLONG_TO_LE (val)) +#define GULONG_FROM_LE(val) (GULONG_TO_LE (val)) +#define GLONG_FROM_BE(val) (GLONG_TO_BE (val)) +#define GULONG_FROM_BE(val) (GULONG_TO_BE (val)) + +#define GINT_FROM_LE(val) (GINT_TO_LE (val)) +#define GUINT_FROM_LE(val) (GUINT_TO_LE (val)) +#define GINT_FROM_BE(val) (GINT_TO_BE (val)) +#define GUINT_FROM_BE(val) (GUINT_TO_BE (val)) + +#define GSIZE_FROM_LE(val) (GSIZE_TO_LE (val)) +#define GSSIZE_FROM_LE(val) (GSSIZE_TO_LE (val)) +#define GSIZE_FROM_BE(val) (GSIZE_TO_BE (val)) +#define GSSIZE_FROM_BE(val) (GSSIZE_TO_BE (val)) + + +/* Portable versions of host-network order stuff + */ +#define g_ntohl(val) (GUINT32_FROM_BE (val)) +#define g_ntohs(val) (GUINT16_FROM_BE (val)) +#define g_htonl(val) (GUINT32_TO_BE (val)) +#define g_htons(val) (GUINT16_TO_BE (val)) + +#include <../../config.h> + +#if WORDS_BIGENDIAN +#define GUINT64_TO_BE(val) (val) +#define GUINT32_TO_BE(val) (val) +#define GUINT16_TO_BE(val) (val) +#define GUINT64_TO_LE(val) GUINT64_SWAP_LE_BE(val) +#define GUINT32_TO_LE(val) GUINT32_SWAP_LE_BE(val) +#define GUINT16_TO_LE(val) GUINT16_SWAP_LE_BE(val) +#define GINT64_TO_BE(val) (val) +#define GINT32_TO_BE(val) (val) +#define GINT16_TO_BE(val) (val) +#define GINT64_TO_LE(val) GUINT64_SWAP_LE_BE(val) +#define GINT32_TO_LE(val) GUINT32_SWAP_LE_BE(val) +#define GINT16_TO_LE(val) GUINT16_SWAP_LE_BE(val) +#else +#define GUINT64_TO_BE(val) GUINT64_SWAP_LE_BE(val) +#define GUINT32_TO_BE(val) GUINT32_SWAP_LE_BE(val) +#define GUINT16_TO_BE(val) GUINT16_SWAP_LE_BE(val) +#define GUINT64_TO_LE(val) (val) +#define GUINT32_TO_LE(val) (val) +#define GUINT16_TO_LE(val) (val) +#define GINT64_TO_BE(val) GUINT64_SWAP_LE_BE(val) +#define GINT32_TO_BE(val) GUINT32_SWAP_LE_BE(val) +#define GINT16_TO_BE(val) GUINT16_SWAP_LE_BE(val) +#define GINT64_TO_LE(val) (val) +#define GINT32_TO_LE(val) (val) +#define GINT16_TO_LE(val) (val) +#endif + +#define LE_16(val) (GINT16_FROM_LE (*((u_int16_t*)(val)))) +#define BE_16(val) (GINT16_FROM_BE (*((u_int16_t*)(val)))) +#define LE_32(val) (GINT32_FROM_LE (*((u_int32_t*)(val)))) +#define BE_32(val) (GINT32_FROM_BE (*((u_int32_t*)(val)))) + +#define LE_64(val) (GINT64_FROM_LE (*((u_int64_t*)(val)))) +#define BE_64(val) (GINT64_FROM_BE (*((u_int64_t*)(val)))) + +#endif /* BSWAP_H_INCLUDED */ diff --git a/plugins/mms/libmms/mms-common.h b/plugins/mms/libmms/mms-common.h new file mode 100644 index 00000000..444d35a0 --- /dev/null +++ b/plugins/mms/libmms/mms-common.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2010 Hans de Goede <j.w.r.degoede@hhs.nl> + * + * This file is part of libmms a free mms protocol library + * + * libmms is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * libmss is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +/* This file contains code which is shared between the mms and mmsh protocol + handling code. */ + +#ifndef __MMS_COMMON_H +#define __MMS_COMMON_H + +typedef struct mms_stream_s mms_stream_t; +struct mms_stream_s { + int stream_id; + int stream_type; + uint32_t bitrate; + uint32_t bitrate_pos; +}; + +#endif diff --git a/plugins/mms/libmms/mms.c b/plugins/mms/libmms/mms.c new file mode 100644 index 00000000..7e0d4a07 --- /dev/null +++ b/plugins/mms/libmms/mms.c @@ -0,0 +1,1802 @@ +/* + * Copyright (C) 2002-2004 the xine project + * + * This file is part of LibMMS, an MMS protocol handling library. + * + * xine is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the ree Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * xine is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + * $Id: mms.c,v 1.31 2007/12/11 20:35:01 jwrdegoede Exp $ + * + * MMS over TCP protocol + * based on work from major mms + * utility functions to handle communication with an mms server + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <unistd.h> +#include <stdio.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <errno.h> +#include <stdlib.h> +#include <time.h> +#include <iconv.h> + +/********** logging **********/ +#define lprintf(...) if (getenv("LIBMMS_DEBUG")) fprintf(stderr, __VA_ARGS__) + +#define __MMS_C__ + +#include "bswap.h" +#include "mms.h" +#include "asfheader.h" +#include "uri.h" +#include "mms-common.h" + +/* + * mms specific types + */ + +#define MMST_PORT 1755 + +#define BUF_SIZE 102400 + +#define CMD_HEADER_LEN 40 +#define CMD_PREFIX_LEN 8 +#define CMD_BODY_LEN 1024 * 16 /* FIXME: make this dynamic */ + +#define ASF_HEADER_LEN (8192 * 2) + + +#define MMS_PACKET_ERR 0 +#define MMS_PACKET_COMMAND 1 +#define MMS_PACKET_ASF_HEADER 2 +#define MMS_PACKET_ASF_PACKET 3 + +#define ASF_HEADER_PACKET_ID_TYPE 2 +#define ASF_MEDIA_PACKET_ID_TYPE 4 + + +typedef struct mms_buffer_s mms_buffer_t; +struct mms_buffer_s { + uint8_t *buffer; + int pos; +}; + +typedef struct mms_packet_header_s mms_packet_header_t; +struct mms_packet_header_s { + uint32_t packet_len; + uint8_t flags; + uint8_t packet_id_type; + uint32_t packet_seq; +}; + +struct mms_s { + + int s; + + /* url parsing */ + GURI *guri; + char *url; + char *proto; + char *host; + int port; + char *user; + char *password; + char *uri; + + /* command to send */ + char scmd[CMD_HEADER_LEN + CMD_BODY_LEN]; + char *scmd_body; /* pointer to &scmd[CMD_HEADER_LEN] */ + int scmd_len; /* num bytes written in header */ + + char str[1024]; /* scratch buffer to built strings */ + + /* receive buffer */ + uint8_t buf[BUF_SIZE]; + int buf_size; + int buf_read; + off_t buf_packet_seq_offset; /* packet sequence offset residing in + buf */ + + uint8_t asf_header[ASF_HEADER_LEN]; + uint32_t asf_header_len; + uint32_t asf_header_read; + int seq_num; + int num_stream_ids; + mms_stream_t streams[ASF_MAX_NUM_STREAMS]; + uint8_t packet_id_type; + off_t start_packet_seq; /* for live streams != 0, need to keep it around */ + int need_discont; /* whether we need to set start_packet_seq */ + uint32_t asf_packet_len; + uint64_t file_len; + uint64_t time_len; /* playback time in 100 nanosecs (10^-7) */ + uint64_t preroll; + uint64_t asf_num_packets; + char guid[37]; + int bandwidth; + + int has_audio; + int has_video; + int live_flag; + int seekable; + off_t current_pos; + int eos; +}; + +static int fallback_io_select(void *data, int socket, int state, int timeout_msec) +{ + fd_set set; + struct timeval tv = { timeout_msec / 1000, (timeout_msec % 1000) * 1000}; + FD_ZERO(&set); + FD_SET(socket, &set); + return select(1, (state == MMS_IO_READ_READY) ? &set : NULL, + (state == MMS_IO_WRITE_READY) ? &set : NULL, NULL, &tv); +} + +static off_t fallback_io_read(void *data, int socket, char *buf, off_t num) +{ + off_t len = 0, ret; +/* lprintf("%d\n", fallback_io_select(data, socket, MMS_IO_READ_READY, 1000)); */ + errno = 0; + while (len < num) + { + ret = (off_t)read(socket, buf + len, num - len); + if(ret == 0) + break; /* EOF */ + if(ret < 0) { + lprintf("mms: read error @ len = %lld: %s\n", (long long int) len, + strerror(errno)); + switch(errno) + { + case EAGAIN: + continue; + default: + /* if already read something, return it, we will fail next time */ + return len ? len : ret; + } + } + len += ret; + } + return len; +} + +static off_t fallback_io_write(void *data, int socket, char *buf, off_t num) +{ + return (off_t)write(socket, buf, num); +} + +static int fallback_io_tcp_connect(void *data, const char *host, int port) +{ + + struct hostent *h; + int i, s; + + h = gethostbyname(host); + if (h == NULL) { + lprintf("mms: unable to resolve host: %s\n", host); + return -1; + } + + s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + if (s == -1) { + lprintf("mms: failed to create socket: %s\n", strerror(errno)); + return -1; + } + + if (fcntl (s, F_SETFL, fcntl (s, F_GETFL) & ~O_NONBLOCK) == -1) { + lprintf("mms: failed to set socket flags: %s\n", strerror(errno)); + return -1; + } + + for (i = 0; h->h_addr_list[i]; i++) { + struct in_addr ia; + struct sockaddr_in sin; + + memcpy (&ia, h->h_addr_list[i], 4); + sin.sin_family = AF_INET; + sin.sin_addr = ia; + sin.sin_port = htons(port); + + if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) ==-1 && errno != EINPROGRESS) { + continue; + } + + return s; + } + close(s); + return -1; +} + + +static mms_io_t fallback_io = + { + &fallback_io_select, + NULL, + &fallback_io_read, + NULL, + &fallback_io_write, + NULL, + &fallback_io_tcp_connect, + NULL, + }; + +static mms_io_t default_io = { + &fallback_io_select, + NULL, + &fallback_io_read, + NULL, + &fallback_io_write, + NULL, + &fallback_io_tcp_connect, + NULL, + }; + + +#define io_read(io, args...) ((io) ? (io)->read(io->read_data , ## args) : default_io.read(NULL , ## args)) +#define io_write(io, args...) ((io) ? (io)->write(io->write_data , ## args) : default_io.write(NULL , ## args)) +#define io_select(io, args...) ((io) ? (io)->select(io->select_data , ## args) : default_io.select(NULL , ## args)) +#define io_connect(io, args...) ((io) ? (io)->connect(io->connect_data , ## args) : default_io.connect(NULL , ## args)) + +const mms_io_t* mms_get_default_io_impl() +{ + return &default_io; +} + +void mms_set_default_io_impl(const mms_io_t *io) +{ + if(io->select) + { + default_io.select = io->select; + default_io.select_data = io->select_data; + } else + { + default_io.select = fallback_io.select; + default_io.select_data = fallback_io.select_data; + } + if(io->read) + { + default_io.read = io->read; + default_io.read_data = io->read_data; + } else + { + default_io.read = fallback_io.read; + default_io.read_data = fallback_io.read_data; + } + if(io->write) + { + default_io.write = io->write; + default_io.write_data = io->write_data; + } else + { + default_io.write = fallback_io.write; + default_io.write_data = fallback_io.write_data; + } + if(io->connect) + { + default_io.connect = io->connect; + default_io.connect_data = io->connect_data; + } else + { + default_io.connect = fallback_io.connect; + default_io.connect_data = fallback_io.connect_data; + } +} + +static void mms_buffer_init (mms_buffer_t *mms_buffer, uint8_t *buffer) { + mms_buffer->buffer = buffer; + mms_buffer->pos = 0; +} + +static void mms_buffer_put_8 (mms_buffer_t *mms_buffer, uint8_t value) { + + mms_buffer->buffer[mms_buffer->pos] = value & 0xff; + + mms_buffer->pos += 1; +} + +#if 0 +static void mms_buffer_put_16 (mms_buffer_t *mms_buffer, uint16_t value) { + + mms_buffer->buffer[mms_buffer->pos] = value & 0xff; + mms_buffer->buffer[mms_buffer->pos + 1] = (value >> 8) & 0xff; + + mms_buffer->pos += 2; +} +#endif + +static void mms_buffer_put_32 (mms_buffer_t *mms_buffer, uint32_t value) { + + mms_buffer->buffer[mms_buffer->pos] = value & 0xff; + mms_buffer->buffer[mms_buffer->pos + 1] = (value >> 8) & 0xff; + mms_buffer->buffer[mms_buffer->pos + 2] = (value >> 16) & 0xff; + mms_buffer->buffer[mms_buffer->pos + 3] = (value >> 24) & 0xff; + + mms_buffer->pos += 4; +} + +static int get_guid (unsigned char *buffer, int offset) { + int i; + GUID g; + + g.Data1 = LE_32(buffer + offset); + g.Data2 = LE_16(buffer + offset + 4); + g.Data3 = LE_16(buffer + offset + 6); + for(i = 0; i < 8; i++) { + g.Data4[i] = buffer[offset + 8 + i]; + } + + for (i = 1; i < GUID_END; i++) { + if (!memcmp(&g, &guids[i].guid, sizeof(GUID))) { + lprintf("mms: GUID: %s\n", guids[i].name); + return i; + } + } + + lprintf("mms: unknown GUID: 0x%x, 0x%x, 0x%x, " + "{ 0x%hx, 0x%hx, 0x%hx, 0x%hx, 0x%hx, 0x%hx, 0x%hx, 0x%hx }\n", + g.Data1, g.Data2, g.Data3, + g.Data4[0], g.Data4[1], g.Data4[2], g.Data4[3], + g.Data4[4], g.Data4[5], g.Data4[6], g.Data4[7]); + + return GUID_ERROR; +} + + +static void print_command (char *data, int len) { + +#ifdef DEBUG + int i; + int dir = LE_32 (data + 36) >> 16; + int comm = LE_32 (data + 36) & 0xFFFF; + + lprintf ("----------------------------------------------\n"); + if (dir == 3) { + lprintf ("send command 0x%02x, %d bytes\n", comm, len); + } else { + lprintf ("receive command 0x%02x, %d bytes\n", comm, len); + } + lprintf (" start sequence %08x\n", LE_32 (data + 0)); + lprintf (" command id %08x\n", LE_32 (data + 4)); + lprintf (" length %8x \n", LE_32 (data + 8)); + lprintf (" protocol %08x\n", LE_32 (data + 12)); + lprintf (" len8 %8x \n", LE_32 (data + 16)); + lprintf (" sequence # %08x\n", LE_32 (data + 20)); + lprintf (" len8 (II) %8x \n", LE_32 (data + 32)); + lprintf (" dir | comm %08x\n", LE_32 (data + 36)); + if (len >= 4) + lprintf (" prefix1 %08x\n", LE_32 (data + 40)); + if (len >= 8) + lprintf (" prefix2 %08x\n", LE_32 (data + 44)); + + for (i = (CMD_HEADER_LEN + CMD_PREFIX_LEN); i < (CMD_HEADER_LEN + CMD_PREFIX_LEN + len); i += 1) { + unsigned char c = data[i]; + + if ((c >= 32) && (c < 128)) + lprintf ("%c", c); + else + lprintf (" %02x ", c); + + } + if (len > CMD_HEADER_LEN) + lprintf ("\n"); + lprintf ("----------------------------------------------\n"); +#endif +} + + + +static int send_command (mms_io_t *io, mms_t *this, int command, + uint32_t prefix1, uint32_t prefix2, + int length) { + int len8; + off_t n; + mms_buffer_t command_buffer; + + len8 = (length + 7) / 8; + + this->scmd_len = 0; + + mms_buffer_init(&command_buffer, this->scmd); + mms_buffer_put_32 (&command_buffer, 0x00000001); /* start sequence */ + mms_buffer_put_32 (&command_buffer, 0xB00BFACE); /* #-)) */ + mms_buffer_put_32 (&command_buffer, len8 * 8 + 32); + mms_buffer_put_32 (&command_buffer, 0x20534d4d); /* protocol type "MMS " */ + mms_buffer_put_32 (&command_buffer, len8 + 4); + mms_buffer_put_32 (&command_buffer, this->seq_num); + this->seq_num++; + mms_buffer_put_32 (&command_buffer, 0x0); /* timestamp */ + mms_buffer_put_32 (&command_buffer, 0x0); + mms_buffer_put_32 (&command_buffer, len8 + 2); + mms_buffer_put_32 (&command_buffer, 0x00030000 | command); /* dir | command */ + /* end of the 40 byte command header */ + + mms_buffer_put_32 (&command_buffer, prefix1); + mms_buffer_put_32 (&command_buffer, prefix2); + + if (length & 7) + memset(this->scmd + length + CMD_HEADER_LEN + CMD_PREFIX_LEN, 0, 8 - (length & 7)); + + n = io_write(io, this->s, this->scmd, len8 * 8 + CMD_HEADER_LEN + CMD_PREFIX_LEN); + if (n != (len8 * 8 + CMD_HEADER_LEN + CMD_PREFIX_LEN)) { + return 0; + } + + print_command (this->scmd, length); + + return 1; +} + +static int string_utf16(iconv_t url_conv, char *dest, char *src, int dest_len) +{ + char *ip = src, *op = dest; + size_t ip_len = strlen(src); + size_t op_len = dest_len - 2; /* reserve 2 bytes for 0 termination */ + + if (iconv(url_conv, &ip, &ip_len, &op, &op_len) == (size_t)-1) { + lprintf("mms: Error converting uri to unicode: %s\n", strerror(errno)); + return 0; + } + + /* 0 terminate the string */ + *op++ = 0; + *op++ = 0; + + return op - dest; +} + +/* + * return packet type + */ +static int get_packet_header (mms_io_t *io, mms_t *this, mms_packet_header_t *header) { + size_t len; + int packet_type; + + header->packet_len = 0; + header->packet_seq = 0; + header->flags = 0; + header->packet_id_type = 0; + len = io_read(io, this->s, this->buf, 8); + this->buf_packet_seq_offset = -1; + if (len != 8) + goto error; + + if (LE_32(this->buf + 4) == 0xb00bface) { + /* command packet */ + header->flags = this->buf[3]; + len = io_read(io, this->s, this->buf + 8, 4); + if (len != 4) + goto error; + + header->packet_len = LE_32(this->buf + 8) + 4; + if (header->packet_len > BUF_SIZE - 12) { + lprintf("mms: get_packet_header error cmd packet length > bufsize\n"); + header->packet_len = 0; + return MMS_PACKET_ERR; + } + packet_type = MMS_PACKET_COMMAND; + } else { + header->packet_seq = LE_32(this->buf); + header->packet_id_type = this->buf[4]; + header->flags = this->buf[5]; + header->packet_len = (LE_16(this->buf + 6) - 8) & 0xffff; + if (header->packet_id_type == ASF_HEADER_PACKET_ID_TYPE) { + packet_type = MMS_PACKET_ASF_HEADER; + } else { + packet_type = MMS_PACKET_ASF_PACKET; + } + } + + return packet_type; + +error: + lprintf("mms: error reading packet header\n"); + return MMS_PACKET_ERR; +} + + +static int get_packet_command (mms_io_t *io, mms_t *this, uint32_t packet_len) { + + + int command = 0; + size_t len; + + len = io_read(io, this->s, this->buf + 12, packet_len) ; + //this->buf_packet_seq_offset = -1; // already set in get_packet_header + if (len != packet_len) { + lprintf("mms: error reading command packet\n"); + return 0; + } + + print_command (this->buf, len); + + /* check protocol type ("MMS ") */ + if (LE_32(this->buf + 12) != 0x20534D4D) { + lprintf("mms: unknown protocol type: %c%c%c%c (0x%08X)\n", + this->buf[12], this->buf[13], this->buf[14], this->buf[15], + LE_32(this->buf + 12)); + return 0; + } + + command = LE_32 (this->buf + 36) & 0xFFFF; + lprintf("mms: received command = %02x, len: %d\n", command, packet_len); + + return command; +} + +static int get_answer (mms_io_t *io, mms_t *this) { + int command = 0; + mms_packet_header_t header; + + switch (get_packet_header (io, this, &header)) { + case MMS_PACKET_ERR: + break; + case MMS_PACKET_COMMAND: + command = get_packet_command (io, this, header.packet_len); + if (command == 0) + return 0; + + if (command == 0x1b) { + if (!send_command (io, this, 0x1b, 0, 0, 0)) { + lprintf("mms: error sending ping reply\n"); + return 0; + } + /* FIXME: limit recursion */ + command = get_answer (io, this); + } + break; + case MMS_PACKET_ASF_HEADER: + lprintf("mms: unexpected asf header packet\n"); + break; + case MMS_PACKET_ASF_PACKET: + lprintf("mms: unexpected asf packet\n"); + break; + } + + return command; +} + + +static int get_asf_header (mms_io_t *io, mms_t *this) { + + off_t len; + int stop = 0; + + this->asf_header_read = 0; + this->asf_header_len = 0; + + while (!stop) { + mms_packet_header_t header; + int command; + + switch (get_packet_header (io, this, &header)) { + case MMS_PACKET_ERR: + return 0; + case MMS_PACKET_COMMAND: + command = get_packet_command (io, this, header.packet_len); + if (command == 0) + return 0; + + if (command == 0x1b) { + if (!send_command (io, this, 0x1b, 0, 0, 0)) { + lprintf("mms: error sending ping reply\n"); + return 0; + } + command = get_answer (io, this); + } else { + lprintf("mms: unexpected command packet\n"); + } + break; + case MMS_PACKET_ASF_HEADER: + case MMS_PACKET_ASF_PACKET: + if (header.packet_len + this->asf_header_len > ASF_HEADER_LEN) { + lprintf("mms: asf packet too large: %d\n", + header.packet_len + this->asf_header_len); + return 0; + } + len = io_read(io, this->s, + this->asf_header + this->asf_header_len, header.packet_len); + if (len != header.packet_len) { + lprintf("mms: error reading asf header\n"); + return 0; + } + this->asf_header_len += header.packet_len; + lprintf("mms: header flags: %d\n", header.flags); + if ((header.flags == 0X08) || (header.flags == 0X0C)) + stop = 1; + break; + } + } + return 1; +} + +static void interp_stream_properties(mms_t *this, int i) +{ + uint16_t flags; + uint16_t stream_id; + int type; + int encrypted; + int guid; + + guid = get_guid(this->asf_header, i); + switch (guid) { + case GUID_ASF_AUDIO_MEDIA: + type = ASF_STREAM_TYPE_AUDIO; + this->has_audio = 1; + break; + + case GUID_ASF_VIDEO_MEDIA: + case GUID_ASF_JFIF_MEDIA: + case GUID_ASF_DEGRADABLE_JPEG_MEDIA: + type = ASF_STREAM_TYPE_VIDEO; + this->has_video = 1; + break; + + case GUID_ASF_COMMAND_MEDIA: + type = ASF_STREAM_TYPE_CONTROL; + break; + + default: + type = ASF_STREAM_TYPE_UNKNOWN; + } + + flags = LE_16(this->asf_header + i + 48); + stream_id = flags & 0x7F; + encrypted = flags >> 15; + + lprintf("mms: stream object, stream id: %d, type: %d, encrypted: %d\n", + stream_id, type, encrypted); + + if (this->num_stream_ids < ASF_MAX_NUM_STREAMS) { + this->streams[this->num_stream_ids].stream_type = type; + this->streams[this->num_stream_ids].stream_id = stream_id; + this->num_stream_ids++; + } else { + lprintf("mms: too many streams, skipping\n"); + } +} + +static void interp_asf_header (mms_t *this) { + + int i; + + this->asf_packet_len = 0; + this->num_stream_ids = 0; + this->asf_num_packets = 0; + /* + * parse header + */ + + i = 30; + while ((i + 24) <= this->asf_header_len) { + + int guid; + uint64_t length; + + guid = get_guid(this->asf_header, i); + length = LE_64(this->asf_header + i + 16); + + if ((i + length) > this->asf_header_len) return; + + switch (guid) { + + case GUID_ASF_FILE_PROPERTIES: + + this->asf_packet_len = LE_32(this->asf_header + i + 92); + if (this->asf_packet_len > BUF_SIZE) { + lprintf("mms: asf packet len too large: %d\n", this->asf_packet_len); + this->asf_packet_len = 0; + break; + } + this->file_len = LE_64(this->asf_header + i + 40); + this->time_len = LE_64(this->asf_header + i + 64); + //this->time_len = LE_64(this->asf_header + i + 72); + this->preroll = LE_64(this->asf_header + i + 80); + lprintf("mms: file object, packet length = %d (%d)\n", + this->asf_packet_len, LE_32(this->asf_header + i + 96)); + break; + + case GUID_ASF_STREAM_PROPERTIES: + interp_stream_properties(this, i + 24); + break; + + case GUID_ASF_STREAM_BITRATE_PROPERTIES: + { + uint16_t streams = LE_16(this->asf_header + i + 24); + uint16_t stream_id; + int j; + + for(j = 0; j < streams; j++) { + int stream_index; + stream_id = LE_16(this->asf_header + i + 24 + 2 + j * 6); + for(stream_index = 0; stream_index < this->num_stream_ids; stream_index++) { + if (this->streams[stream_index].stream_id == stream_id) + break; + } + if (stream_index < this->num_stream_ids) { + this->streams[stream_index].bitrate = LE_32(this->asf_header + i + 24 + 4 + j * 6); + this->streams[stream_index].bitrate_pos = i + 24 + 4 + j * 6; + lprintf("mms: stream id %d, bitrate %d\n", stream_id, + this->streams[stream_index].bitrate); + } else + lprintf ("mms: unknown stream id %d in bitrate properties\n", + stream_id); + } + } + break; + + case GUID_ASF_HEADER_EXTENSION: + { + if ((24 + 18 + 4) > length) + break; + + int size = LE_32(this->asf_header + i + 24 + 18); + int j = 24 + 18 + 4; + int l; + lprintf("mms: Extension header data size: %d\n", size); + + while ((j + 24) <= length) { + guid = get_guid(this->asf_header, i + j); + l = LE_64(this->asf_header + i + j + 16); + + if ((j + l) > length) + break; + + if (guid == GUID_ASF_EXTENDED_STREAM_PROPERTIES && + (24 + 64) <= l) { + int stream_no = LE_16(this->asf_header + i + j + 24 + 48); + int name_count = LE_16(this->asf_header + i + j + 24 + 60); + int ext_count = LE_16(this->asf_header + i + j + 24 + 62); + int ext_j = 24 + 64; + int x; + + lprintf("mms: l: %d\n", l); + lprintf("mms: Stream No: %d\n", stream_no); + lprintf("mms: ext_count: %d\n", ext_count); + + // Loop through the number of stream names + for (x = 0; x < name_count && (ext_j + 4) <= l; x++) { + int lang_id_index; + int stream_name_len; + + lang_id_index = LE_16(this->asf_header + i + j + ext_j); + ext_j += 2; + + stream_name_len = LE_16(this->asf_header + i + j + ext_j); + ext_j += stream_name_len + 2; + + lprintf("mms: Language id index: %d\n", lang_id_index); + lprintf("mms: Stream name Len: %d\n", stream_name_len); + } + + // Loop through the number of extension system info + for (x = 0; x < ext_count && (ext_j + 22) <= l; x++) { + ext_j += 18; + int len = LE_16(this->asf_header + i + j + ext_j); + ext_j += 4 + len; + } + + lprintf("mms: ext_j: %d\n", ext_j); + // Finally, we arrive at the interesting point: The optional Stream Property Object + if ((ext_j + 24) <= l) { + guid = get_guid(this->asf_header, i + j + ext_j); + int len = LE_64(this->asf_header + i + j + ext_j + 16); + if (guid == GUID_ASF_STREAM_PROPERTIES && + (ext_j + len) <= l) { + interp_stream_properties(this, i + j + ext_j + 24); + } + } else { + lprintf("mms: Sorry, field not long enough\n"); + } + } + j += l; + } + } + break; + + case GUID_ASF_DATA: + this->asf_num_packets = LE_64(this->asf_header + i + 40 - 24); + break; + } + + lprintf("mms: length: %llu\n", (unsigned long long)length); + i += length; + } +} + +const static char *const mmst_proto_s[] = { "mms", "mmst", NULL }; + +static int mmst_valid_proto (char *proto) { + int i = 0; + + if (!proto) + return 0; + + while(mmst_proto_s[i]) { + if (!strcasecmp(proto, mmst_proto_s[i])) { + return 1; + } + i++; + } + return 0; +} + +/* + * returns 1 on error + */ +static int mms_tcp_connect(mms_io_t *io, mms_t *this) { + if (!this->port) this->port = MMST_PORT; + + /* + * try to connect + */ + lprintf("mms: trying to connect to %s on port %d\n", this->host, this->port); + this->s = io_connect(io, this->host, this->port); + if (this->s == -1) { + lprintf("mms: failed to connect to %s\n", this->host); + return 1; + } + + lprintf("mms: connected\n"); + return 0; +} + +static void mms_gen_guid(char guid[]) { + static char digit[16] = "0123456789ABCDEF"; + int i = 0; + + srand(time(NULL)); + for (i = 0; i < 36; i++) { + guid[i] = digit[(int) ((16.0*rand())/(RAND_MAX+1.0))]; + } + guid[8] = '-'; guid[13] = '-'; guid[18] = '-'; guid[23] = '-'; + guid[36] = '\0'; +} + +const char *status_to_string(int status) +{ + switch (status) { + case 0x80070003: + return "Path not found"; + case 0x80070005: + return "Access Denied"; + default: + return "Unknown"; + } +} + +/* + * return 0 on error + */ +int static mms_choose_best_streams(mms_io_t *io, mms_t *this) { + int i; + int video_stream = -1; + int audio_stream = -1; + int max_arate = 0; + int min_vrate = 0; + int min_bw_left = 0; + int bandwitdh_left; + int res; + + /* command 0x33 */ + /* choose the best quality for the audio stream */ + /* i've never seen more than one audio stream */ + for (i = 0; i < this->num_stream_ids; i++) { + switch (this->streams[i].stream_type) { + case ASF_STREAM_TYPE_AUDIO: + if (this->streams[i].bitrate > max_arate) { + audio_stream = this->streams[i].stream_id; + max_arate = this->streams[i].bitrate; + } + break; + default: + break; + } + } + + /* choose a video stream adapted to the user bandwidth */ + bandwitdh_left = this->bandwidth - max_arate; + if (bandwitdh_left < 0) { + bandwitdh_left = 0; + } + lprintf("mms: bandwidth %d, left %d\n", this->bandwidth, bandwitdh_left); + + min_bw_left = bandwitdh_left; + for (i = 0; i < this->num_stream_ids; i++) { + switch (this->streams[i].stream_type) { + case ASF_STREAM_TYPE_VIDEO: + if (((bandwitdh_left - this->streams[i].bitrate) < min_bw_left) && + (bandwitdh_left >= this->streams[i].bitrate)) { + video_stream = this->streams[i].stream_id; + min_bw_left = bandwitdh_left - this->streams[i].bitrate; + } + break; + default: + break; + } + } + + /* choose the lower bitrate of */ + if (video_stream == -1 && this->has_video) { + for (i = 0; i < this->num_stream_ids; i++) { + switch (this->streams[i].stream_type) { + case ASF_STREAM_TYPE_VIDEO: + if ((this->streams[i].bitrate < min_vrate) || + (!min_vrate)) { + video_stream = this->streams[i].stream_id; + min_vrate = this->streams[i].bitrate; + } + break; + default: + break; + } + } + } + + lprintf("mms: selected streams: audio %d, video %d\n", audio_stream, video_stream); + memset (this->scmd_body, 0, 40); + for (i = 1; i < this->num_stream_ids; i++) { + this->scmd_body [ (i - 1) * 6 + 2 ] = 0xFF; + this->scmd_body [ (i - 1) * 6 + 3 ] = 0xFF; + this->scmd_body [ (i - 1) * 6 + 4 ] = this->streams[i].stream_id ; + this->scmd_body [ (i - 1) * 6 + 5 ] = this->streams[i].stream_id >> 8; + if ((this->streams[i].stream_id == audio_stream) || + (this->streams[i].stream_id == video_stream)) { + this->scmd_body [ (i - 1) * 6 + 6 ] = 0x00; + this->scmd_body [ (i - 1) * 6 + 7 ] = 0x00; + } else { + lprintf("mms: disabling stream %d\n", this->streams[i].stream_id); + this->scmd_body [ (i - 1) * 6 + 6 ] = 0x02; + this->scmd_body [ (i - 1) * 6 + 7 ] = 0x00; + + /* forces the asf demuxer to not choose this stream */ + if (this->streams[i].bitrate_pos) { + if (this->streams[i].bitrate_pos+3 < ASF_HEADER_LEN) { + this->asf_header[this->streams[i].bitrate_pos ] = 0; + this->asf_header[this->streams[i].bitrate_pos + 1] = 0; + this->asf_header[this->streams[i].bitrate_pos + 2] = 0; + this->asf_header[this->streams[i].bitrate_pos + 3] = 0; + } else { + lprintf("mms: attempt to write beyond asf header limit\n"); + } + } + } + } + + lprintf("mms: send command 0x33\n"); + if (!send_command (io, this, 0x33, this->num_stream_ids, + 0xFFFF | this->streams[0].stream_id << 16, + this->num_stream_ids * 6 + 2)) { + lprintf("mms: mms_choose_best_streams failed\n"); + return 0; + } + + if ((res = get_answer (io, this)) != 0x21) { + lprintf("mms: unexpected response: %02x (0x21)\n", res); + return 0; + } + + res = LE_32(this->buf + 40); + if (res != 0) { + lprintf("mms: error answer 0x21 status: %08x (%s)\n", + res, status_to_string(res)); + return 0; + } + + return 1; +} + +/* + * TODO: error messages + * network timing request + */ +/* FIXME: got somewhat broken during xine_stream_t->(void*) conversion */ +mms_t *mms_connect (mms_io_t *io, void *data, const char *url, int bandwidth) { + iconv_t url_conv = (iconv_t)-1; + mms_t *this; + int res; + uint32_t openid; + mms_buffer_t command_buffer; + + if (!url) + return NULL; + + /* FIXME: needs proper error-signalling work */ + this = (mms_t*) malloc (sizeof (mms_t)); + + this->url = strdup (url); + this->s = -1; + this->seq_num = 0; + this->scmd_body = this->scmd + CMD_HEADER_LEN + CMD_PREFIX_LEN; + this->asf_header_len = 0; + this->asf_header_read = 0; + this->num_stream_ids = 0; + this->asf_packet_len = 0; + this->start_packet_seq= 0; + this->need_discont = 1; + this->buf_size = 0; + this->buf_read = 0; + this->buf_packet_seq_offset = -1; + this->has_audio = 0; + this->has_video = 0; + this->bandwidth = bandwidth; + this->current_pos = 0; + this->eos = 0; + + this->guri = gnet_uri_new(this->url); + if(!this->guri) { + lprintf("mms: invalid url\n"); + goto fail; + } + + /* MMS wants unescaped (so not percent coded) strings */ + gnet_uri_unescape(this->guri); + + this->proto = this->guri->scheme; + this->user = this->guri->user; + this->host = this->guri->hostname; + this->port = this->guri->port; + this->password = this->guri->passwd; + this->uri = gnet_mms_helper(this->guri, 0); + + if(!this->uri) + goto fail; + + if (!mmst_valid_proto(this->proto)) { + lprintf("mms: unsupported protocol: %s\n", this->proto); + goto fail; + } + + if (mms_tcp_connect(io, this)) { + goto fail; + } + + url_conv = iconv_open("UTF-16LE", "UTF-8"); + if (url_conv == (iconv_t)-1) { + lprintf("mms: could not get iconv handle to convert url to unicode\n"); + goto fail; + } + + /* + * let the negotiations begin... + */ + + /* command 0x1 */ + lprintf("mms: send command 0x01\n"); + mms_buffer_init(&command_buffer, this->scmd_body); + mms_buffer_put_32 (&command_buffer, 0x0003001C); + mms_gen_guid(this->guid); + sprintf(this->str, "NSPlayer/7.0.0.1956; {%s}; Host: %s", this->guid, + this->host); + res = string_utf16(url_conv, this->scmd_body + command_buffer.pos, this->str, + CMD_BODY_LEN - command_buffer.pos); + if(!res) + goto fail; + + if (!send_command(io, this, 1, 0, 0x0004000b, command_buffer.pos + res)) { + lprintf("mms: failed to send command 0x01\n"); + goto fail; + } + + if ((res = get_answer (io, this)) != 0x01) { + lprintf("mms: unexpected response: %02x (0x01)\n", res); + goto fail; + } + + res = LE_32(this->buf + 40); + if (res != 0) { + lprintf("mms: error answer 0x01 status: %08x (%s)\n", + res, status_to_string(res)); + goto fail; + } + + /* TODO: insert network timing request here */ + /* command 0x2 */ + lprintf("mms: send command 0x02\n"); + mms_buffer_init(&command_buffer, this->scmd_body); + mms_buffer_put_32 (&command_buffer, 0x00000000); + mms_buffer_put_32 (&command_buffer, 0x00989680); + mms_buffer_put_32 (&command_buffer, 0x00000002); + res = string_utf16(url_conv, this->scmd_body + command_buffer.pos, + "\\\\192.168.0.129\\TCP\\1037", + CMD_BODY_LEN - command_buffer.pos); + if(!res) + goto fail; + + if (!send_command(io, this, 2, 0, 0xffffffff, command_buffer.pos + res)) { + lprintf("mms: failed to send command 0x02\n"); + goto fail; + } + + switch (res = get_answer (io, this)) { + case 0x02: + /* protocol accepted */ + break; + case 0x03: + lprintf("mms: protocol failed\n"); + goto fail; + default: + lprintf("mms: unexpected response: %02x (0x02 or 0x03)\n", res); + goto fail; + } + + res = LE_32(this->buf + 40); + if (res != 0) { + lprintf("mms: error answer 0x02 status: %08x (%s)\n", + res, status_to_string(res)); + goto fail; + } + + /* command 0x5 */ + { + mms_buffer_t command_buffer; + + lprintf("mms: send command 0x05\n"); + mms_buffer_init(&command_buffer, this->scmd_body); + mms_buffer_put_32 (&command_buffer, 0x00000000); /* ?? */ + mms_buffer_put_32 (&command_buffer, 0x00000000); /* ?? */ + + res = string_utf16(url_conv, this->scmd_body + command_buffer.pos, + this->uri, CMD_BODY_LEN - command_buffer.pos); + if(!res) + goto fail; + + if (!send_command(io, this, 5, 1, 0, command_buffer.pos + res)) { + lprintf("mms: failed to send command 0x05\n"); + goto fail; + } + } + + switch (res = get_answer (io, this)) { + case 0x06: + { + int xx, yy; + /* no authentication required */ + openid = LE_32(this->buf + 48); + + /* Warning: sdp is not right here */ + xx = this->buf[62]; + yy = this->buf[63]; + this->live_flag = ((xx == 0) && ((yy & 0xf) == 2)); + this->seekable = !this->live_flag; + lprintf("mms: openid=%d, live: live_flag=%d, xx=%d, yy=%d\n", openid, this->live_flag, xx, yy); + } + break; + case 0x1A: + /* authentication request, not yet supported */ + lprintf("mms: authentication request, not yet supported\n"); + goto fail; + break; + default: + lprintf("mms: unexpected response: %02x (0x06 or 0x1A)\n", res); + goto fail; + } + + res = LE_32(this->buf + 40); + if (res != 0) { + lprintf("mms: error answer 0x06 status: %08x (%s)\n", + res, status_to_string(res)); + goto fail; + } + + /* command 0x15 */ + lprintf("mms: send command 0x15\n"); + { + mms_buffer_t command_buffer; + mms_buffer_init(&command_buffer, this->scmd_body); + mms_buffer_put_32 (&command_buffer, 0x00000000); /* ?? */ + mms_buffer_put_32 (&command_buffer, 0x00008000); /* ?? */ + mms_buffer_put_32 (&command_buffer, 0xFFFFFFFF); /* ?? */ + mms_buffer_put_32 (&command_buffer, 0x00000000); /* ?? */ + mms_buffer_put_32 (&command_buffer, 0x00000000); /* ?? */ + mms_buffer_put_32 (&command_buffer, 0x00000000); /* ?? */ + mms_buffer_put_32 (&command_buffer, 0x00000000); /* ?? */ + mms_buffer_put_32 (&command_buffer, 0x40AC2000); /* ?? */ + mms_buffer_put_32 (&command_buffer, ASF_HEADER_PACKET_ID_TYPE); /* Header Packet ID type */ + mms_buffer_put_32 (&command_buffer, 0x00000000); /* ?? */ + if (!send_command (io, this, 0x15, openid, 0, command_buffer.pos)) { + lprintf("mms: failed to send command 0x15\n"); + goto fail; + } + } + + if ((res = get_answer (io, this)) != 0x11) { + lprintf("mms: unexpected response: %02x (0x11)\n", res); + goto fail; + } + + res = LE_32(this->buf + 40); + if (res != 0) { + lprintf("mms: error answer 0x11 status: %08x (%s)\n", + res, status_to_string(res)); + goto fail; + } + + this->num_stream_ids = 0; + + if (!get_asf_header (io, this)) + goto fail; + + interp_asf_header (this); + if (!this->asf_packet_len || !this->num_stream_ids) + goto fail; + + if (!mms_choose_best_streams(io, this)) { + lprintf("mms: mms_choose_best_streams failed\n"); + goto fail; + } + + /* command 0x07 */ + this->packet_id_type = ASF_MEDIA_PACKET_ID_TYPE; + { + mms_buffer_t command_buffer; + mms_buffer_init(&command_buffer, this->scmd_body); + mms_buffer_put_32 (&command_buffer, 0x00000000); /* 64 byte float timestamp */ + mms_buffer_put_32 (&command_buffer, 0x00000000); + mms_buffer_put_32 (&command_buffer, 0xFFFFFFFF); /* ?? */ + mms_buffer_put_32 (&command_buffer, 0xFFFFFFFF); /* first packet sequence */ + mms_buffer_put_8 (&command_buffer, 0xFF); /* max stream time limit (3 bytes) */ + mms_buffer_put_8 (&command_buffer, 0xFF); + mms_buffer_put_8 (&command_buffer, 0xFF); + mms_buffer_put_8 (&command_buffer, 0x00); /* stream time limit flag */ + mms_buffer_put_32 (&command_buffer, this->packet_id_type); /* asf media packet id type */ + if (!send_command (io, this, 0x07, 1, 0x0001FFFF, command_buffer.pos)) { + lprintf("mms: failed to send command 0x07\n"); + goto fail; + } + } + + iconv_close(url_conv); + lprintf("mms: connect: passed\n"); + + return this; + +fail: + if (this->s != -1) + close (this->s); + if (this->url) + free(this->url); + if (this->guri) + gnet_uri_delete(this->guri); + if (this->uri) + free(this->uri); + if (url_conv != (iconv_t)-1) + iconv_close(url_conv); + + free (this); + return NULL; +} + +static int get_media_packet (mms_io_t *io, mms_t *this) { + mms_packet_header_t header; + off_t len; + + switch (get_packet_header (io, this, &header)) { + case MMS_PACKET_ERR: + return 0; + + case MMS_PACKET_COMMAND: + { + int command; + command = get_packet_command (io, this, header.packet_len); + + switch (command) { + case 0: + return 0; + + case 0x1e: + { + uint32_t error_code; + + /* Warning: sdp is incomplete. Do not stop if error_code==1 */ + error_code = LE_32(this->buf + CMD_HEADER_LEN); + lprintf("mms: End of the current stream. Continue=%d\n", error_code); + + if (error_code == 0) { + this->eos = 1; + return 0; + } + + } + break; + + case 0x20: + { + lprintf("mms: new stream.\n"); + /* asf header */ + if (!get_asf_header (io, this)) { + lprintf("mms: failed to read new ASF header\n"); + return 0; + } + + interp_asf_header (this); + if (!this->asf_packet_len || !this->num_stream_ids) + return 0; + + if (!mms_choose_best_streams(io, this)) + return 0; + + /* send command 0x07 */ + /* TODO: ugly */ + /* command 0x07 */ + { + mms_buffer_t command_buffer; + mms_buffer_init(&command_buffer, this->scmd_body); + mms_buffer_put_32 (&command_buffer, 0x00000000); /* 64 byte float timestamp */ + mms_buffer_put_32 (&command_buffer, 0x00000000); + mms_buffer_put_32 (&command_buffer, 0xFFFFFFFF); /* ?? */ + mms_buffer_put_32 (&command_buffer, 0xFFFFFFFF); /* first packet sequence */ + mms_buffer_put_8 (&command_buffer, 0xFF); /* max stream time limit (3 bytes) */ + mms_buffer_put_8 (&command_buffer, 0xFF); + mms_buffer_put_8 (&command_buffer, 0xFF); + mms_buffer_put_8 (&command_buffer, 0x00); /* stream time limit flag */ + mms_buffer_put_32 (&command_buffer, ASF_MEDIA_PACKET_ID_TYPE); /* asf media packet id type */ + if (!send_command (io, this, 0x07, 1, 0x0001FFFF, command_buffer.pos)) { + lprintf("mms: failed to send command 0x07\n"); + return 0; + } + } + this->current_pos = 0; + + /* I don't know if this ever happens with none live (and thus + seekable streams), but I do know that if it happens all bets + with regards to seeking are off */ + this->seekable = 0; + } + break; + + case 0x1b: + { + if (!send_command (io, this, 0x1b, 0, 0, 0)) { + lprintf("mms: error sending ping reply\n"); + return 0; + } + } + break; + + case 0x05: + break; + + default: + lprintf("mms: unexpected mms command %02x\n", command); + } + this->buf_size = 0; + } + break; + + case MMS_PACKET_ASF_HEADER: + lprintf("mms: unexpected asf header packet\n"); + this->buf_size = 0; + break; + + case MMS_PACKET_ASF_PACKET: + { + /* media packet */ + + /* FIXME: probably needs some more sophisticated logic, but + until we do seeking, this should work */ + if(this->need_discont && + header.packet_id_type == ASF_MEDIA_PACKET_ID_TYPE) + { + this->need_discont = 0; + this->start_packet_seq = header.packet_seq; + } + + if (header.packet_len > this->asf_packet_len) { + lprintf("mms: invalid asf packet len: %d bytes\n", header.packet_len); + return 0; + } + + /* simulate a seek */ + this->current_pos = (off_t)this->asf_header_len + + ((off_t)header.packet_seq - this->start_packet_seq) * (off_t)this->asf_packet_len; + + len = io_read(io, this->s, this->buf, header.packet_len); + if (len != header.packet_len) { + lprintf("mms: error reading asf packet\n"); + return 0; + } + + /* explicit padding with 0 */ + memset(this->buf + header.packet_len, 0, this->asf_packet_len - header.packet_len); + if (header.packet_id_type == this->packet_id_type) { + this->buf_size = this->asf_packet_len; + this->buf_packet_seq_offset = + header.packet_seq - this->start_packet_seq; + } else { + this->buf_size = 0; + // Don't set this packet sequence for reuse in seek(), since the + // subsequence packet may be discontinued. + //this->buf_packet_seq_offset = header.packet_seq; + // already set to -1 in get_packet_header + //this->buf_packet_seq_offset = -1; + } + } + break; + } + + return 1; +} + + +int mms_peek_header (mms_t *this, char *data, int maxsize) { + + int len; + + len = (this->asf_header_len < maxsize) ? this->asf_header_len : maxsize; + + memcpy(data, this->asf_header, len); + return len; +} + +int mms_read (mms_io_t *io, mms_t *this, char *data, int len) { + int total; + + total = 0; + while (total < len && !this->eos) { + + if (this->asf_header_read < this->asf_header_len) { + int n, bytes_left; + + bytes_left = this->asf_header_len - this->asf_header_read ; + + if ((len - total) < bytes_left) + n = len-total; + else + n = bytes_left; + + memcpy (&data[total], &this->asf_header[this->asf_header_read], n); + + this->asf_header_read += n; + total += n; + this->current_pos += n; + } else { + + int n, bytes_left; + + bytes_left = this->buf_size - this->buf_read; + if (bytes_left == 0) { + this->buf_size = this->buf_read = 0; + if (!get_media_packet (io, this)) { + lprintf("mms: get_media_packet failed\n"); + return total; + } + bytes_left = this->buf_size; + } + + if ((len - total) < bytes_left) + n = len - total; + else + n = bytes_left; + + memcpy (&data[total], &this->buf[this->buf_read], n); + + this->buf_read += n; + total += n; + this->current_pos += n; + } + } + return total; +} + +// To be inline function? +static int mms_request_data_packet (mms_io_t *io, mms_t *this, + double time_sec, unsigned long first_packet, unsigned long time_msec_limit) { + /* command 0x07 */ + { + mms_buffer_t command_buffer; + //mms_buffer_init(&command_buffer, this->scmd_body); + //mms_buffer_put_32 (&command_buffer, 0x00000000); /* 64 byte float timestamp */ + //mms_buffer_put_32 (&command_buffer, 0x00000000); + memcpy(this->scmd_body, &time_sec, 8); + mms_buffer_init(&command_buffer, this->scmd_body+8); + mms_buffer_put_32 (&command_buffer, 0xFFFFFFFF); /* ?? */ + //mms_buffer_put_32 (&command_buffer, 0xFFFFFFFF); /* first packet sequence */ + mms_buffer_put_32 (&command_buffer, first_packet); /* first packet sequence */ + //mms_buffer_put_8 (&command_buffer, 0xFF); /* max stream time limit (3 bytes) */ + //mms_buffer_put_8 (&command_buffer, 0xFF); + //mms_buffer_put_8 (&command_buffer, 0xFF); + //mms_buffer_put_8 (&command_buffer, 0x00); /* stream time limit flag */ + mms_buffer_put_32 (&command_buffer, time_msec_limit & 0x00FFFFFF);/* max stream time limit (3 bytes) */ + mms_buffer_put_32 (&command_buffer, this->packet_id_type); /* asf media packet id type */ + if (!send_command (io, this, 0x07, 1, 0x0001FFFF, 8+command_buffer.pos)) { + lprintf("mms: failed to send command 0x07\n"); + return 0; + } + } + /* TODO: adjust current_pos, considering asf_header_read */ + return 1; +} + +int mms_request_time_seek (mms_io_t *io, mms_t *this, double time_sec) { + if (++this->packet_id_type <= ASF_MEDIA_PACKET_ID_TYPE) + this->packet_id_type = ASF_MEDIA_PACKET_ID_TYPE+1; + //return mms_request_data_packet (io, this, time_sec, 0xFFFFFFFF, 0x00FFFFFF); + // also adjust time by preroll + return mms_request_data_packet (io, this, + time_sec+(double)(this->preroll)/1000, + 0xFFFFFFFF, 0x00FFFFFF); +} + +// set current_pos to the first byte of the requested packet by peeking at +// the first packet. +// To be inline function? +static int peek_and_set_pos (mms_io_t *io, mms_t *this) { + uint8_t saved_buf[BUF_SIZE]; + int saved_buf_size; + off_t saved_buf_packet_seq_offset; + // save buf and buf_size that may be changed in get_media_packet() + memcpy(saved_buf, this->buf, this->buf_size); + saved_buf_size = this->buf_size; + saved_buf_packet_seq_offset = this->buf_packet_seq_offset; + //this->buf_size = this->buf_read = 0; // reset buf, only if success peeking + this->buf_size = 0; + while (!this->eos) { + // get_media_packet() will set current_pos if data packet is read. + if (!get_media_packet (io, this)) { + lprintf("mms: get_media_packet failed\n"); + // restore buf and buf_size that may be changed in get_media_packet() + memcpy(this->buf, saved_buf, saved_buf_size); + this->buf_size = saved_buf_size; + this->buf_packet_seq_offset = saved_buf_packet_seq_offset; + return 0; + } + if (this->buf_size > 0) break; + } + // flush header and reset buf_read, only if success peeking + this->asf_header_read = this->asf_header_len; + this->buf_read = 0; + return 1; + //return this->current_pos; +} + +// send time seek request, and update current_pos corresponding to the next +// requested packet +// Note that, the current_pos will always does not less than asf_header_len +int mms_time_seek (mms_io_t *io, mms_t *this, double time_sec) { + if (!this->seekable) + return 0; + + if (!mms_request_time_seek (io, this, time_sec)) return 0; + return peek_and_set_pos (io, this); +} + +// http://sdp.ppona.com/zipfiles/MMSprotocol_pdf.zip said that, this +// packet_seq value make no difference in version 9 servers. +// But from my experiment with +// mms://202.142.200.130/tltk/56k/tltkD2006-08-08ID-7209.wmv and +// mms://202.142.200.130/tltk/56k/tltkD2006-09-01ID-7467.wmv (the url may valid +// in only 2-3 months) whose server is version 9, it does response and return +// the requested packet. +int mms_request_packet_seek (mms_io_t *io, mms_t *this, + unsigned long packet_seq) { + if (++this->packet_id_type <= ASF_MEDIA_PACKET_ID_TYPE) + this->packet_id_type = ASF_MEDIA_PACKET_ID_TYPE+1; + return mms_request_data_packet (io, this, 0, packet_seq, 0x00FFFFFF); +} + +// send packet seek request, and update current_pos corresponding to the next +// requested packet +// Note that, the current_pos will always does not less than asf_header_len +// Not export this function. Let user use mms_seek() instead? +static int mms_packet_seek (mms_io_t *io, mms_t *this, + unsigned long packet_seq) { + if (!mms_request_packet_seek (io, this, packet_seq)) return 0; + return peek_and_set_pos (io, this); +} + +/* +TODO: To use this table to calculate buf_packet_seq_offset rather than store +and retrieve it from this->buf_packet_seq_offset? +current_packet_seq == (current_pos - asf_header_len) / asf_packet_len +current_packet_seq == -1 if current_pos < asf_header_len +buf_packet_seq_offset indicating which packet sequence are residing in the buf. +Possible status after read(), "last" means last value or unchange. +current_packet_seq | buf_read | buf_size | buf_packet_seq_offset +-------------------+----------------+-----------+--------------- +<= 0 | 0 (last) | 0 (last) | none +<= 0 | 0 (last) | 0 (last) | eos at #0 +<= 0 | 0 (last) | 0 (last) | eos at > #0 +<= 0 | 0 (last) | > 0 (last)| #0 +<= 0 | buf_size (last)| > 0 (last)| > #0 +> 0 | 0 | 0 | eos at current_packet_seq +> 0 | 0(never happen)| > 0 | (never happen) +> 0 | buf_size | > 0 | current_packet_seq-1 +*/ +// TODO: How to handle seek() in multi stream source? +// The stream that follows 0x20 ("new stream") command. +off_t mms_seek (mms_io_t *io, mms_t *this, off_t offset, int origin) { + off_t dest; + off_t dest_packet_seq; + //off_t buf_packet_seq_offset; + + if (!this->seekable) + return this->current_pos; + + switch (origin) { + case SEEK_SET: + dest = offset; + break; + case SEEK_CUR: + dest = this->current_pos + offset; + break; + case SEEK_END: + //if (this->asf_num_packets == 0) { + // //printf ("input_mms: unknown end position in seek!\n"); + // return this->current_pos; + //} + dest = mms_get_length (this) + offset; + default: + printf ("input_mms: unknown origin in seek!\n"); + return this->current_pos; + } + + dest_packet_seq = dest - this->asf_header_len; + //if (dest_packet_seq > 0) dest_packet_seq /= this->asf_packet_len; + dest_packet_seq = dest_packet_seq >= 0 ? + dest_packet_seq / this->asf_packet_len : -1; +#if 0 + // buf_packet_seq_offset will identify which packet sequence are residing in + // the buf. +#if 1 /* To show both of the alternate styles :D */ + buf_packet_seq_offset = this->current_pos - this->asf_header_len; + //if (buf_packet_seq_offset > 0) buf_packet_seq_offset /= this->asf_packet_len; + buf_packet_seq_offset = buf_packet_seq_offset >= 0 ? + buf_packet_seq_offset / this->asf_packet_len : -1; + // Note: buf_read == buf_size == 0 may means that it is eos, + // eos means that the packet has been peek'ed. + if (this->buf_read >= this->buf_size && this->buf_size > 0 && + buf_packet_seq_offset >= 0 || + // assuming packet not peek'ed in the following condition + /*this->buf_read >= this->buf_size && */this->buf_size == 0 && + buf_packet_seq_offset == 0) + // The buf is all read but the packet has not been peek'ed. + --buf_packet_seq_offset; +#else + buf_packet_seq_offset = this->current_pos - this->asf_header_len - 1; + //if (buf_packet_seq_offset > 0) buf_packet_seq_offset /= this->asf_packet_len; + buf_packet_seq_offset = buf_packet_seq_offset >= 0 ? + buf_packet_seq_offset / this->asf_packet_len : -1; + // Note: buf_read == buf_size == 0 may means that it is eos, + // eos means that the packet has been peek'ed. + if (this->buf_read == 0/* && buf_packet_seq_offset >= 0*/) + // Since the packet has just been peek'ed. + ++buf_packet_seq_offset; +#endif +#endif + + if (dest_packet_seq < 0) { + if (this->buf_packet_seq_offset > 0) { + if (!mms_request_packet_seek (io, this, 0xFFFFFFFF)) + return this->current_pos; +#if 1 + // clear buf + this->buf_read = this->buf_size = 0; + this->buf_packet_seq_offset = -1; + } else { +#else + // clear buf + this->buf_read = this->buf_size; + // Set this packet sequence not to be reused, since the subsequence + // packet may be discontinued. + this->buf_packet_seq_offset = -1; + // don't reset buf_read if buf_packet_seq_offset < 0, since the previous + // buf may not be cleared. + } else if (this->buf_packet_seq_offset == 0) { +#endif + // reset buf_read + this->buf_read = 0; + } + this->asf_header_read = dest; + return this->current_pos = dest; + } + // dest_packet_seq >= 0 + if (this->asf_num_packets && dest == this->asf_header_len + + this->asf_num_packets*this->asf_packet_len) { + // Requesting the packet beyond the last packet, can cause the server to + // not return any packet or any eos command. This can cause + // mms_packet_seek() to hang. + // This is to allow seeking at end of stream, and avoid hanging. + --dest_packet_seq; + } + if (dest_packet_seq != this->buf_packet_seq_offset) { + if (this->asf_num_packets && dest_packet_seq >= this->asf_num_packets) { + // Do not seek beyond the last packet. + return this->current_pos; + } + if (!mms_packet_seek (io, this, this->start_packet_seq + dest_packet_seq)) + return this->current_pos; + // Check if current_pos is correct. + // This can happen if the server ignore packet seek command. + // If so, just return unchanged current_pos, rather than trying to + // mms_read() to reach the destination pos. + // It should let the caller to decide to choose the alternate method, such + // as, mms_time_seek() and/or mms_read() until the destination pos is + // reached. + if (dest_packet_seq != this->buf_packet_seq_offset) + return this->current_pos; + // This has already been set in mms_packet_seek(). + //if (current_packet_seq < 0) + // this->asf_header_read = this->asf_header_len; + //this->asf_header_read = this->asf_header_len; + } + // eos is reached ? + //if (this->buf_size <= 0) return this->current_pos; + //this->buf_read = (dest - this->asf_header_len) % this->asf_packet_len; + this->buf_read = dest - + (this->asf_header_len + dest_packet_seq*this->asf_packet_len); + // will never happen ? + //if (this->buf_size <= this->buf_read) return this->current_pos; + return this->current_pos = dest; +} + + +void mms_close (mms_t *this) { + + if (this->s != -1) + close (this->s); + if (this->url) + free(this->url); + if (this->guri) + gnet_uri_delete(this->guri); + if (this->uri) + free(this->uri); + + free (this); +} + +double mms_get_time_length (mms_t *this) { + return (double)(this->time_len) / 1e7; +} + +uint64_t mms_get_raw_time_length (mms_t *this) { + return this->time_len; +} + +uint32_t mms_get_length (mms_t *this) { + /* we could / should return this->file_len here, but usually this->file_len + is longer then the calculation below, as usually an asf file contains an + asf index object after the data stream. However since we do not have a + (known) way to get to this index object through mms, we return a + calculated size of what we can get to when we know. */ + if (this->asf_num_packets) + return this->asf_header_len + this->asf_num_packets*this->asf_packet_len; + else + return this->file_len; +} + +off_t mms_get_current_pos (mms_t *this) { + return this->current_pos; +} + +uint32_t mms_get_asf_header_len (mms_t *this) { + return this->asf_header_len; +} + +uint64_t mms_get_asf_packet_len (mms_t *this) { + return this->asf_packet_len; +} + +int mms_get_seekable (mms_t *this) { + return this->seekable; +} diff --git a/plugins/mms/libmms/mms.h b/plugins/mms/libmms/mms.h new file mode 100644 index 00000000..bce71d54 --- /dev/null +++ b/plugins/mms/libmms/mms.h @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2002-2003 the xine project + * + * This file is part of xine, a free video player. + * + * xine is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * xine is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + * $Id: mms.h,v 1.15 2007/12/11 20:24:48 jwrdegoede Exp $ + * + * libmms public header + */ + +#ifndef HAVE_MMS_H +#define HAVE_MMS_H + +#include <inttypes.h> +#include <stdio.h> +#include <sys/types.h> + +#include "mmsio.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef struct mms_s mms_t; + +mms_t* mms_connect (mms_io_t *io, void *data, const char *url, int bandwidth); + +int mms_read (mms_io_t *io, mms_t *instance, char *data, int len); +int mms_request_time_seek (mms_io_t *io, mms_t *instance, double time_sec); +int mms_time_seek (mms_io_t *io, mms_t *instance, double time_sec); +int mms_request_packet_seek (mms_io_t *io, mms_t *instance, + unsigned long packet_seq); +/* + * mms_seek() will try to seek using mms_request_packet_seek(), if the server + * ignore the packet seek command, it will return unchanged current_pos, rather + * than trying to mms_read() until the destination pos is reached. This is to + * let the caller, by itself, to decide to choose the alternate method, such + * as, mms_time_seek() and/or mms_read() until the destination pos is reached. + * One can do binary search using time offset (mms_time_seek()) as a search + * index, to approach the desired byte offset. It is to systematically guess + * the time offset to reach for the byte offset. + */ +mms_off_t mms_seek (mms_io_t *io, mms_t *instance, mms_off_t offset, int origin); +/* return total playback time in seconds */ +double mms_get_time_length (mms_t *instance); +/* return raw total playback time in 100 nanosecs (10^-7) */ +uint64_t mms_get_raw_time_length (mms_t *instance); +uint32_t mms_get_length (mms_t *instance); +void mms_close (mms_t *instance); + +int mms_peek_header (mms_t *instance, char *data, int maxsize); + +mms_off_t mms_get_current_pos (mms_t *instance); + +uint32_t mms_get_asf_header_len (mms_t *instance); + +uint64_t mms_get_asf_packet_len (mms_t *instance); + +int mms_get_seekable (mms_t *instance); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif + diff --git a/plugins/mms/libmms/mmsh.c b/plugins/mms/libmms/mmsh.c new file mode 100644 index 00000000..9a7b119b --- /dev/null +++ b/plugins/mms/libmms/mmsh.c @@ -0,0 +1,1534 @@ +/* + * Copyright (C) 2002-2003 the xine project + * + * This file is part of xine, a free video player. + * + * xine is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * xine is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + * $Id: mmsh.c,v 1.16 2007/12/11 20:50:43 jwrdegoede Exp $ + * + * MMS over HTTP protocol + * written by Thibaut Mattern + * based on mms.c and specs from avifile + * (http://avifile.sourceforge.net/asf-1.0.htm) + * + * TODO: + * error messages + * http support cleanup, find a way to share code with input_http.c (http.h|c) + * http proxy support + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <errno.h> +#include <stdlib.h> +#include <time.h> +#include <assert.h> + +#define lprintf(...) if (getenv("LIBMMS_DEBUG")) fprintf(stderr, __VA_ARGS__) + +/* cheat a bit and call ourselves mms.c to keep the code in mmsio.h clean */ +#define __MMS_C__ + +#include "bswap.h" +#include "mmsh.h" +#include "asfheader.h" +#include "uri.h" +#include "mms-common.h" + +/* #define USERAGENT "User-Agent: NSPlayer/7.1.0.3055\r\n" */ +#define USERAGENT "User-Agent: NSPlayer/4.1.0.3856\r\n" +#define CLIENTGUID "Pragma: xClientGUID={c77e7400-738a-11d2-9add-0020af0a3278}\r\n" + + +#define MMSH_PORT 80 +#define MMSH_UNKNOWN 0 +#define MMSH_SEEKABLE 1 +#define MMSH_LIVE 2 + +#define CHUNK_HEADER_LENGTH 4 +#define EXT_HEADER_LENGTH 8 +#define CHUNK_TYPE_RESET 0x4324 +#define CHUNK_TYPE_DATA 0x4424 +#define CHUNK_TYPE_END 0x4524 +#define CHUNK_TYPE_ASF_HEADER 0x4824 +#define CHUNK_SIZE 65536 /* max chunk size */ +#define ASF_HEADER_SIZE (8192 * 2) /* max header size */ + +#define SCRATCH_SIZE 1024 + +#define SUCCESS 0 +#define ERROR 1 +#define EOS 2 +#define GOT_HEADER_N_DATA 3 + +static const char* mmsh_FirstRequest = + "GET %s HTTP/1.0\r\n" + "Accept: */*\r\n" + USERAGENT + "Host: %s:%d\r\n" + "Pragma: no-cache,rate=1.000000,stream-time=0,stream-offset=0:0,request-context=%u,max-duration=0\r\n" + CLIENTGUID + "Connection: Close\r\n\r\n"; + +static const char* mmsh_SeekableRequest = + "GET %s HTTP/1.0\r\n" + "Accept: */*\r\n" + USERAGENT + "Host: %s:%d\r\n" + "Pragma: no-cache,rate=1.000000,stream-time=%u,stream-offset=%u:%u,request-context=%u,max-duration=%u\r\n" + CLIENTGUID + "Pragma: xPlayStrm=1\r\n" + "Pragma: stream-switch-count=%d\r\n" + "Pragma: stream-switch-entry=%s\r\n" /* ffff:1:0 ffff:2:0 */ + "Connection: Close\r\n\r\n"; + +static const char* mmsh_LiveRequest = + "GET %s HTTP/1.0\r\n" + "Accept: */*\r\n" + USERAGENT + "Host: %s:%d\r\n" + "Pragma: no-cache,rate=1.000000,request-context=%u\r\n" + "Pragma: xPlayStrm=1\r\n" + CLIENTGUID + "Pragma: stream-switch-count=%d\r\n" + "Pragma: stream-switch-entry=%s\r\n" + "Connection: Close\r\n\r\n"; + + +#if 0 +/* Unused requests */ +static const char* mmsh_PostRequest = + "POST %s HTTP/1.0\r\n" + "Accept: */*\r\n" + USERAGENT + "Host: %s\r\n" + "Pragma: client-id=%u\r\n" +/* "Pragma: log-line=no-cache,rate=1.000000,stream-time=%u,stream-offset=%u:%u,request-context=2,max-duration=%u\r\n" */ + "Pragma: Content-Length: 0\r\n" + CLIENTGUID + "\r\n"; + +static const char* mmsh_RangeRequest = + "GET %s HTTP/1.0\r\n" + "Accept: */*\r\n" + USERAGENT + "Host: %s:%d\r\n" + "Range: bytes=%Lu-\r\n" + CLIENTGUID + "Connection: Close\r\n\r\n"; +#endif + + + +/* + * mmsh specific types + */ + + +struct mmsh_s { + + int s; + + /* url parsing */ + char *url; + char *proxy_url; + char *proto; + char *connect_host; + int connect_port; + char *http_host; + int http_port; + int http_request_number; + char *proxy_user; + char *proxy_password; + char *host_user; + char *host_password; + char *uri; + + char str[SCRATCH_SIZE]; /* scratch buffer to built strings */ + + int stream_type; /* seekable or broadcast */ + + /* receive buffer */ + + /* chunk */ + uint16_t chunk_type; + uint16_t chunk_length; + uint32_t chunk_seq_number; + uint8_t buf[CHUNK_SIZE]; + + int buf_size; + int buf_read; + + uint8_t asf_header[ASF_HEADER_SIZE]; + uint32_t asf_header_len; + uint32_t asf_header_read; + int num_stream_ids; + mms_stream_t streams[ASF_MAX_NUM_STREAMS]; + uint32_t packet_length; + int64_t file_length; + uint64_t time_len; /* playback time in 100 nanosecs (10^-7) */ + uint64_t preroll; + uint64_t asf_num_packets; + char guid[37]; + + int has_audio; + int has_video; + int seekable; + + off_t current_pos; + int user_bandwidth; +}; + +static int fallback_io_select(void *data, int socket, int state, int timeout_msec) +{ + fd_set set; + struct timeval tv = { timeout_msec / 1000, (timeout_msec % 1000) * 1000}; + FD_ZERO(&set); + FD_SET(socket, &set); + return select(1, (state == MMS_IO_READ_READY) ? &set : NULL, + (state == MMS_IO_WRITE_READY) ? &set : NULL, NULL, &tv); +} + +static off_t fallback_io_read(void *data, int socket, char *buf, off_t num) +{ + off_t len = 0, ret; +/* lprintf("%d\n", fallback_io_select(data, socket, MMS_IO_READ_READY, 1000)); */ + errno = 0; + while (len < num) + { + ret = (off_t)read(socket, buf + len, num - len); + if(ret == 0) + break; /* EOS */ + if(ret < 0) { + lprintf("mmsh: read error @ len = %lld: %s\n", (long long int) len, + strerror(errno)); + switch(errno) + { + case EAGAIN: + continue; + default: + /* if already read something, return it, we will fail next time */ + return len ? len : ret; + } + } + len += ret; + } + return len; +} + +static off_t fallback_io_write(void *data, int socket, char *buf, off_t num) +{ + return (off_t)write(socket, buf, num); +} + +static int fallback_io_tcp_connect(void *data, const char *host, int port) +{ + + struct hostent *h; + int i, s; + + h = gethostbyname(host); + if (h == NULL) { + lprintf("mmsh: unable to resolve host: %s\n", host); + return -1; + } + + s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + if (s == -1) { + lprintf("mmsh: failed to create socket: %s\n", strerror(errno)); + return -1; + } + + if (fcntl (s, F_SETFL, fcntl (s, F_GETFL) & ~O_NONBLOCK) == -1) { + lprintf("mmsh: failed to set socket flags: %s\n", strerror(errno)); + return -1; + } + + for (i = 0; h->h_addr_list[i]; i++) { + struct in_addr ia; + struct sockaddr_in sin; + + memcpy (&ia, h->h_addr_list[i], 4); + sin.sin_family = AF_INET; + sin.sin_addr = ia; + sin.sin_port = htons(port); + + if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) ==-1 && errno != EINPROGRESS) { + continue; + } + + return s; + } + close(s); + return -1; +} + + +static mms_io_t fallback_io = + { + &fallback_io_select, + NULL, + &fallback_io_read, + NULL, + &fallback_io_write, + NULL, + &fallback_io_tcp_connect, + NULL, + }; + +static mms_io_t default_io = { + &fallback_io_select, + NULL, + &fallback_io_read, + NULL, + &fallback_io_write, + NULL, + &fallback_io_tcp_connect, + NULL, + }; + + +#define io_read(io, args...) ((io) ? (io)->read(io->read_data , ## args) : default_io.read(NULL , ## args)) +#define io_write(io, args...) ((io) ? (io)->write(io->write_data , ## args) : default_io.write(NULL , ## args)) +#define io_select(io, args...) ((io) ? (io)->select(io->select_data , ## args) : default_io.select(NULL , ## args)) +#define io_connect(io, args...) ((io) ? (io)->connect(io->connect_data , ## args) : default_io.connect(NULL , ## args)) + +static int get_guid (unsigned char *buffer, int offset) { + int i; + GUID g; + + g.Data1 = LE_32(buffer + offset); + g.Data2 = LE_16(buffer + offset + 4); + g.Data3 = LE_16(buffer + offset + 6); + for(i = 0; i < 8; i++) { + g.Data4[i] = buffer[offset + 8 + i]; + } + + for (i = 1; i < GUID_END; i++) { + if (!memcmp(&g, &guids[i].guid, sizeof(GUID))) { + lprintf("mmsh: GUID: %s\n", guids[i].name); + return i; + } + } + + lprintf("mmsh: unknown GUID: 0x%x, 0x%x, 0x%x, " + "{ 0x%hx, 0x%hx, 0x%hx, 0x%hx, 0x%hx, 0x%hx, 0x%hx, 0x%hx }\n", + g.Data1, g.Data2, g.Data3, + g.Data4[0], g.Data4[1], g.Data4[2], g.Data4[3], + g.Data4[4], g.Data4[5], g.Data4[6], g.Data4[7]); + return GUID_ERROR; +} + +static int send_command (mms_io_t *io, mmsh_t *this, char *cmd) { + int length; + + lprintf("mmsh: send_command:\n%s\n", cmd); + + length = strlen(cmd); + if (io_write(io, this->s, cmd, length) != length) { + lprintf ("mmsh: send error.\n"); + return 0; + } + return 1; +} + +static int get_answer (mms_io_t *io, mmsh_t *this) { + + int done, len, linenum; + char *features; + + done = 0; len = 0; linenum = 0; + this->stream_type = MMSH_UNKNOWN; + + while (!done) { + + if (io_read(io, this->s, &(this->buf[len]), 1) != 1) { + lprintf ("mmsh: alart: end of stream\n"); + return 0; + } + + if (this->buf[len] == '\012') { + + this->buf[len] = '\0'; + len--; + + if ((len >= 0) && (this->buf[len] == '\015')) { + this->buf[len] = '\0'; + len--; + } + + linenum++; + + lprintf("mmsh: answer: >%s<\n", this->buf); + + if (linenum == 1) { + int httpver, httpsub, httpcode; + char httpstatus[51]; + + if (sscanf(this->buf, "HTTP/%d.%d %d %50[^\015\012]", &httpver, &httpsub, + &httpcode, httpstatus) != 4) { + lprintf ("mmsh: bad response format\n"); + return 0; + } + + if (httpcode >= 300 && httpcode < 400) { + lprintf ("mmsh: 3xx redirection not implemented: >%d %s<\n", httpcode, httpstatus); + return 0; + } + + if (httpcode < 200 || httpcode >= 300) { + lprintf ("mmsh: http status not 2xx: >%d %s<\n", httpcode, httpstatus); + return 0; + } + } else { + + if (!strncasecmp(this->buf, "Location: ", 10)) { + lprintf ("mmsh: Location redirection not implemented.\n"); + return 0; + } + + if (!strncasecmp(this->buf, "Pragma:", 7)) { + features = strstr(this->buf + 7, "features="); + if (features) { + if (strstr(features, "seekable")) { + lprintf("mmsh: seekable stream\n"); + this->stream_type = MMSH_SEEKABLE; + this->seekable = 1; + } else { + if (strstr(features, "broadcast")) { + lprintf("mmsh: live stream\n"); + this->stream_type = MMSH_LIVE; + this->seekable = 0; + } + } + } + } + } + + if (len == -1) { + done = 1; + } else { + len = 0; + } + } else { + len ++; + } + } + if (this->stream_type == MMSH_UNKNOWN) { + lprintf ("mmsh: unknown stream type\n"); + this->stream_type = MMSH_SEEKABLE; /* FIXME ? */ + this->seekable = 1; + } + return 1; +} + +static int get_chunk_header (mms_io_t *io, mmsh_t *this) { + uint8_t chunk_header[CHUNK_HEADER_LENGTH]; + uint8_t ext_header[EXT_HEADER_LENGTH]; + int read_len; + int ext_header_len; + + /* read chunk header */ + read_len = io_read(io, this->s, chunk_header, CHUNK_HEADER_LENGTH); + if (read_len != CHUNK_HEADER_LENGTH) { + if (read_len == 0) + return EOS; + lprintf("mmsh: chunk header read failed, %d != %d\n", read_len, CHUNK_HEADER_LENGTH); + return ERROR; + } + this->chunk_type = LE_16 (&chunk_header[0]); + this->chunk_length = LE_16 (&chunk_header[2]); + + switch (this->chunk_type) { + case CHUNK_TYPE_DATA: + ext_header_len = 8; + break; + case CHUNK_TYPE_END: + ext_header_len = 4; + break; + case CHUNK_TYPE_ASF_HEADER: + ext_header_len = 8; + break; + case CHUNK_TYPE_RESET: + ext_header_len = 4; + break; + default: + ext_header_len = 0; + } + /* read extended header */ + if (ext_header_len > 0) { + read_len = io_read (io, this->s, ext_header, ext_header_len); + if (read_len != ext_header_len) { + lprintf("mmsh: extended header read failed. %d != %d\n", read_len, ext_header_len); + return ERROR; + } + } + + if (this->chunk_type == CHUNK_TYPE_DATA || this->chunk_type == CHUNK_TYPE_END) + this->chunk_seq_number = LE_32 (&ext_header[0]); + + /* display debug infos */ +#ifdef DEBUG + switch (this->chunk_type) { + case CHUNK_TYPE_DATA: + lprintf ("chunk type: CHUNK_TYPE_DATA\n"); + lprintf ("chunk length: %d\n", this->chunk_length); + lprintf ("chunk seq: %d\n", this->chunk_seq_number); + lprintf ("unknown: %d\n", ext_header[4]); + lprintf ("mmsh seq: %d\n", ext_header[5]); + lprintf ("len2: %d\n", LE_16(&ext_header[6])); + break; + case CHUNK_TYPE_END: + lprintf ("chunk type: CHUNK_TYPE_END\n"); + lprintf ("continue: %d\n", this->chunk_seq_number); + break; + case CHUNK_TYPE_ASF_HEADER: + lprintf ("chunk type: CHUNK_TYPE_ASF_HEADER\n"); + lprintf ("chunk length: %d\n", this->chunk_length); + lprintf ("unknown: %2X %2X %2X %2X %2X %2X\n", + ext_header[0], ext_header[1], ext_header[2], ext_header[3], + ext_header[4], ext_header[5]); + lprintf ("len2: %d\n", LE_16(&ext_header[6])); + break; + case CHUNK_TYPE_RESET: + lprintf ("chunk type: CHUNK_TYPE_RESET\n"); + lprintf ("chunk seq: %d\n", this->chunk_seq_number); + lprintf ("unknown: %2X %2X %2X %2X\n", + ext_header[0], ext_header[1], ext_header[2], ext_header[3]); + break; + default: + lprintf ("unknown chunk: %4X\n", this->chunk_type); + } +#endif + + this->chunk_length -= ext_header_len; + return SUCCESS; +} + +static int get_header (mms_io_t *io, mmsh_t *this) { + int ret, len = 0; + + this->asf_header_len = 0; + this->asf_header_read = 0; + this->buf_size = 0; + + /* read chunk */ + while (1) { + if ((ret = get_chunk_header(io, this)) == SUCCESS) { + if (this->chunk_type == CHUNK_TYPE_ASF_HEADER) { + if ((this->asf_header_len + this->chunk_length) > ASF_HEADER_SIZE) { + lprintf ("mmsh: the asf header exceed %d bytes\n", ASF_HEADER_SIZE); + return ERROR; + } else { + len = io_read(io, this->s, this->asf_header + this->asf_header_len, + this->chunk_length); + if (len > 0) + this->asf_header_len += len; + if (len != this->chunk_length) { + lprintf ("mmsh: asf header chunk read failed, %d != %d\n", len, + this->chunk_length); + return ERROR; + } + } + } else { + break; + } + } else { + if (this->asf_header_len == 0 || ret != EOS) + lprintf("mmsh: get_header failed to get chunk header\n"); + return ret; + } + } + + if (this->chunk_type == CHUNK_TYPE_DATA) { + /* read the first data chunk */ + len = io_read (io, this->s, this->buf, this->chunk_length); + + if (len != this->chunk_length) { + lprintf ("mmsh: asf data chunk read failed, %d != %d\n", len, + this->chunk_length); + return ERROR; + } else { + /* check and 0 pad the first data chunk */ + if (this->chunk_length > this->packet_length) { + lprintf ("mmsh: chunk_length(%d) > packet_length(%d)\n", + this->chunk_length, this->packet_length); + return ERROR; + } + + /* explicit padding with 0 */ + if (this->chunk_length < this->packet_length) + memset(this->buf + this->chunk_length, 0, + this->packet_length - this->chunk_length); + + this->buf_size = this->packet_length; + + return SUCCESS; + } + } else { + /* unexpected packet type */ + lprintf ("mmsh: unexpected chunk_type(0x%04x)\n", this->chunk_type); + return ERROR; + } +} + +static void interp_stream_properties(mmsh_t *this, int i) { + uint16_t flags; + uint16_t stream_id; + int type; + int encrypted; + int guid; + + guid = get_guid(this->asf_header, i); + switch (guid) { + case GUID_ASF_AUDIO_MEDIA: + type = ASF_STREAM_TYPE_AUDIO; + this->has_audio = 1; + break; + + case GUID_ASF_VIDEO_MEDIA: + case GUID_ASF_JFIF_MEDIA: + case GUID_ASF_DEGRADABLE_JPEG_MEDIA: + type = ASF_STREAM_TYPE_VIDEO; + this->has_video = 1; + break; + + case GUID_ASF_COMMAND_MEDIA: + type = ASF_STREAM_TYPE_CONTROL; + break; + + default: + type = ASF_STREAM_TYPE_UNKNOWN; + } + + flags = LE_16(this->asf_header + i + 48); + stream_id = flags & 0x7F; + encrypted = flags >> 15; + + lprintf("mmsh: stream object, stream id: %d, type: %d, encrypted: %d\n", + stream_id, type, encrypted); + + if (this->num_stream_ids >= ASF_MAX_NUM_STREAMS) { + lprintf("mmsh: too many streams, skipping\n"); + return; + } + + this->streams[this->num_stream_ids].stream_type = type; + this->streams[this->num_stream_ids].stream_id = stream_id; + this->num_stream_ids++; +} + +static void interp_header (mms_io_t *io, mmsh_t *this) { + + int i; + + this->packet_length = 0; + this->num_stream_ids = 0; + this->asf_num_packets = 0; + + /* + * parse asf header + */ + + i = 30; + while ((i + 24) <= this->asf_header_len) { + + int guid; + uint64_t length; + + guid = get_guid(this->asf_header, i); + length = LE_64(this->asf_header + i + 16); + + if ((i + length) > this->asf_header_len) return; + + switch (guid) { + + case GUID_ASF_FILE_PROPERTIES: + + this->packet_length = LE_32(this->asf_header + i + 92); + if (this->packet_length > CHUNK_SIZE) { + this->packet_length = 0; + break; + } + this->file_length = LE_64(this->asf_header + i + 40); + this->time_len = LE_64(this->asf_header + i + 64); + //this->time_len = LE_64(this->asf_header + i + 72); + this->preroll = LE_64(this->asf_header + i + 80); + lprintf("mmsh: file object, packet length = %d (%d)\n", + this->packet_length, LE_32(this->asf_header + i + 96)); + break; + + case GUID_ASF_STREAM_PROPERTIES: + interp_stream_properties(this, i + 24); + break; + + case GUID_ASF_STREAM_BITRATE_PROPERTIES: + { + uint16_t streams = LE_16(this->asf_header + i + 24); + uint16_t stream_id; + int j, stream_index; + + for(j = 0; j < streams; j++) { + stream_id = LE_16(this->asf_header + i + 24 + 2 + j * 6); + + for(stream_index = 0; stream_index < this->num_stream_ids; stream_index++) { + if (this->streams[stream_index].stream_id == stream_id) + break; + } + if (stream_index < this->num_stream_ids) { + this->streams[stream_index].bitrate = LE_32(this->asf_header + i + 24 + 4 + j * 6); + this->streams[stream_index].bitrate_pos = i + 24 + 4 + j * 6; + lprintf ("mmsh: stream id %d, bitrate %d\n", stream_id, + this->streams[stream_index].bitrate); + } else + lprintf ("mmsh: unknown stream id %d in bitrate properties\n", + stream_id); + } + } + break; + + case GUID_ASF_HEADER_EXTENSION: + { + if ((24 + 18 + 4) > length) + break; + + int size = LE_32(this->asf_header + i + 24 + 18); + int j = 24 + 18 + 4; + int l; + lprintf("mmsh: Extension header data size: %d\n", size); + + while ((j + 24) <= length) { + guid = get_guid(this->asf_header, i + j); + l = LE_64(this->asf_header + i + j + 16); + + if ((j + l) > length) + break; + + if (guid == GUID_ASF_EXTENDED_STREAM_PROPERTIES && + (24 + 64) <= l) { + int stream_no = LE_16(this->asf_header + i + j + 24 + 48); + int name_count = LE_16(this->asf_header + i + j + 24 + 60); + int ext_count = LE_16(this->asf_header + i + j + 24 + 62); + int ext_j = 24 + 64; + int x; + + lprintf("mmsh: l: %d\n", l); + lprintf("mmsh: Stream No: %d\n", stream_no); + lprintf("mmsh: ext_count: %d\n", ext_count); + + // Loop through the number of stream names + for (x = 0; x < name_count && (ext_j + 4) <= l; x++) { + int lang_id_index; + int stream_name_len; + + lang_id_index = LE_16(this->asf_header + i + j + ext_j); + ext_j += 2; + + stream_name_len = LE_16(this->asf_header + i + j + ext_j); + ext_j += stream_name_len + 2; + + lprintf("mmsh: Language id index: %d\n", lang_id_index); + lprintf("mmsh: Stream name Len: %d\n", stream_name_len); + } + + // Loop through the number of extension system info + for (x = 0; x < ext_count && (ext_j + 22) <= l; x++) { + ext_j += 18; + int len = LE_16(this->asf_header + i + j + ext_j); + ext_j += 4 + len; + } + + lprintf("mmsh: ext_j: %d\n", ext_j); + // Finally, we arrive at the interesting point: The optional Stream Property Object + if ((ext_j + 24) <= l) { + guid = get_guid(this->asf_header, i + j + ext_j); + int len = LE_64(this->asf_header + i + j + ext_j + 16); + if (guid == GUID_ASF_STREAM_PROPERTIES && + (ext_j + len) <= l) { + interp_stream_properties(this, i + j + ext_j + 24); + } + } else { + lprintf("mmsh: Sorry, field not long enough\n"); + } + } + j += l; + } + } + break; + + case GUID_ASF_DATA: + this->asf_num_packets = LE_64(this->asf_header + i + 40 - 24); + lprintf("mmsh: num_packets: %d\n", (int)this->asf_num_packets); + break; + } + + lprintf("mmsh: length: %llu\n", (unsigned long long)length); + i += length; + } +} + +const static char *const mmsh_proto_s[] = { "mms", "mmsh", NULL }; + +static int mmsh_valid_proto (char *proto) { + int i = 0; + + if (!proto) + return 0; + + while(mmsh_proto_s[i]) { + if (!strcasecmp(proto, mmsh_proto_s[i])) { + return 1; + } + i++; + } + return 0; +} + +/* + * returns 1 on error + */ +static int mmsh_tcp_connect(mms_io_t *io, mmsh_t *this) { + if (!this->connect_port) this->connect_port = MMSH_PORT; + + /* + * try to connect + */ + lprintf("mmsh: try to connect to %s on port %d \n", this->connect_host, this->connect_port); + + this->s = io_connect (io, this->connect_host, this->connect_port); + + if (this->s == -1) { + lprintf("mmsh: failed to connect '%s'\n", this->connect_host); + return 1; + } + + lprintf("mmsh: connected\n"); + + return 0; +} + +static int mmsh_connect_int (mms_io_t *io, mmsh_t *this, off_t seek, uint32_t time_seek) { + int i; + int video_stream = -1; + int audio_stream = -1; + int max_arate = -1; + int min_vrate = -1; + int min_bw_left = 0; + int bandwitdh_left; + char stream_selection[10 * ASF_MAX_NUM_STREAMS]; /* 10 chars per stream */ + int offset; + + /* Close exisiting connection (if any) and connect */ + if (this->s != -1) + close(this->s); + + if (mmsh_tcp_connect(io, this)) { + return 0; + } + + /* + * let the negotiations begin... + */ + this->num_stream_ids = 0; + + /* first request */ + lprintf("mmsh: first http request\n"); + + snprintf (this->str, SCRATCH_SIZE, mmsh_FirstRequest, this->uri, + this->http_host, this->http_port, this->http_request_number++); + + if (!send_command (io, this, this->str)) + goto fail; + + if (!get_answer (io, this)) + goto fail; + + /* Don't check for != SUCCESS as EOS is normal here too */ + if (get_header(io, this) == ERROR) + goto fail; + + interp_header(io, this); + if (!this->packet_length || !this->num_stream_ids) + goto fail; + + close(this->s); + + /* choose the best quality for the audio stream */ + /* i've never seen more than one audio stream */ + for (i = 0; i < this->num_stream_ids; i++) { + switch (this->streams[i].stream_type) { + case ASF_STREAM_TYPE_AUDIO: + if ((audio_stream == -1) || (this->streams[i].bitrate > max_arate)) { + audio_stream = this->streams[i].stream_id; + max_arate = this->streams[i].bitrate; + } + break; + default: + break; + } + } + + /* choose a video stream adapted to the user bandwidth */ + bandwitdh_left = this->user_bandwidth - max_arate; + if (bandwitdh_left < 0) { + bandwitdh_left = 0; + } + lprintf("mmsh: bandwitdh %d, left %d\n", this->user_bandwidth, bandwitdh_left); + + min_bw_left = bandwitdh_left; + for (i = 0; i < this->num_stream_ids; i++) { + switch (this->streams[i].stream_type) { + case ASF_STREAM_TYPE_VIDEO: + if (((bandwitdh_left - this->streams[i].bitrate) < min_bw_left) && + (bandwitdh_left >= this->streams[i].bitrate)) { + video_stream = this->streams[i].stream_id; + min_bw_left = bandwitdh_left - this->streams[i].bitrate; + } + break; + default: + break; + } + } + + /* choose the stream with the lower bitrate */ + if ((video_stream == -1) && this->has_video) { + for (i = 0; i < this->num_stream_ids; i++) { + switch (this->streams[i].stream_type) { + case ASF_STREAM_TYPE_VIDEO: + if ((video_stream == -1) || + (this->streams[i].bitrate < min_vrate) || + (!min_vrate)) { + video_stream = this->streams[i].stream_id; + min_vrate = this->streams[i].bitrate; + } + break; + default: + break; + } + } + } + + lprintf("mmsh: audio stream %d, video stream %d\n", audio_stream, video_stream); + + /* second request */ + lprintf("mmsh: second http request\n"); + + if (mmsh_tcp_connect(io, this)) { + return 0; + } + + /* stream selection string */ + /* The same selection is done with mmst */ + /* 0 means selected */ + /* 2 means disabled */ + offset = 0; + for (i = 0; i < this->num_stream_ids; i++) { + int size; + if ((this->streams[i].stream_id == audio_stream) || + (this->streams[i].stream_id == video_stream)) { + size = snprintf(stream_selection + offset, sizeof(stream_selection) - offset, + "ffff:%d:0 ", this->streams[i].stream_id); + } else { + lprintf("mmsh: disabling stream %d\n", this->streams[i].stream_id); + size = snprintf(stream_selection + offset, sizeof(stream_selection) - offset, + "ffff:%d:2 ", this->streams[i].stream_id); + } + if (size < 0) goto fail; + offset += size; + } + + switch (this->stream_type) { + case MMSH_SEEKABLE: + snprintf (this->str, SCRATCH_SIZE, mmsh_SeekableRequest, this->uri, + this->http_host, this->http_port, time_seek, + (unsigned int)(seek >> 32), + (unsigned int)seek, this->http_request_number++, 0, + this->num_stream_ids, stream_selection); + break; + case MMSH_LIVE: + snprintf (this->str, SCRATCH_SIZE, mmsh_LiveRequest, this->uri, + this->http_host, this->http_port, this->http_request_number++, + this->num_stream_ids, stream_selection); + break; + } + + if (!send_command (io, this, this->str)) + goto fail; + + if (!get_answer (io, this)) + goto fail; + + if (get_header(io, this) != SUCCESS) + goto fail; + + interp_header(io, this); + if (!this->packet_length || !this->num_stream_ids) + goto fail; + + for (i = 0; i < this->num_stream_ids; i++) { + if ((this->streams[i].stream_id != audio_stream) && + (this->streams[i].stream_id != video_stream)) { + lprintf("disabling stream %d\n", this->streams[i].stream_id); + + /* forces the asf demuxer to not choose this stream */ + if (this->streams[i].bitrate_pos) { + if (this->streams[i].bitrate_pos + 3 < ASF_HEADER_SIZE) { + this->asf_header[this->streams[i].bitrate_pos] = 0; + this->asf_header[this->streams[i].bitrate_pos + 1] = 0; + this->asf_header[this->streams[i].bitrate_pos + 2] = 0; + this->asf_header[this->streams[i].bitrate_pos + 3] = 0; + } else { + lprintf("mmsh: attempt to write beyond asf header limit"); + } + } + } + } + return 1; +fail: + close(this->s); + this->s = -1; + return 0; +} + +mmsh_t *mmsh_connect (mms_io_t *io, void *data, const char *url, int bandwidth) { + mmsh_t *this; + GURI *uri = NULL; + GURI *proxy_uri = NULL; + char *proxy_env; + if (!url) + return NULL; + + /* + * initializatoin is essential here. the fail: label depends + * on the various char * in our this structure to be + * NULL if they haven't been assigned yet. + */ + this = (mmsh_t*) malloc (sizeof (mmsh_t)); + this->url=NULL; + this->proxy_url = NULL; + this->proto = NULL; + this->connect_host = NULL; + this->http_host = NULL; + this->proxy_user = NULL; + this->proxy_password = NULL; + this->host_user = NULL; + this->host_password = NULL; + this->uri = NULL; + + this->url = strdup(url); + if ((proxy_env = getenv("http_proxy")) != NULL) + this->proxy_url = strdup(proxy_env); + else + this->proxy_url = NULL; + this->s = -1; + this->asf_header_len = 0; + this->asf_header_read = 0; + this->num_stream_ids = 0; + this->packet_length = 0; + this->buf_size = 0; + this->buf_read = 0; + this->has_audio = 0; + this->has_video = 0; + this->current_pos = 0; + this->user_bandwidth = bandwidth; + this->http_request_number = 1; + + if (this->proxy_url) { + proxy_uri = gnet_uri_new(this->proxy_url); + if (!proxy_uri) { + lprintf("mmsh: invalid proxy url\n"); + goto fail; + } + if (! proxy_uri->port ) { + proxy_uri->port = 3128; //default squid port + } + } + uri = gnet_uri_new(this->url); + if (!uri) { + lprintf("mmsh: invalid url\n"); + goto fail; + } + if (! uri->port ) { + //checked in tcp_connect, but it's better to initialize it here + uri->port = MMSH_PORT; + } + if (this->proxy_url) { + this->proto = (uri->scheme) ? strdup(uri->scheme) : NULL; + this->connect_host = (proxy_uri->hostname) ? strdup(proxy_uri->hostname) : NULL; + this->connect_port = proxy_uri->port; + this->http_host = (uri->scheme) ? strdup(uri->hostname) : NULL; + this->http_port = uri->port; + this->proxy_user = (proxy_uri->user) ? strdup(proxy_uri->user) : NULL; + this->proxy_password = (proxy_uri->passwd) ? strdup(proxy_uri->passwd) : NULL; + this->host_user = (uri->user) ? strdup(uri->user) : NULL; + this->host_password = (uri->passwd) ? strdup(uri->passwd) : NULL; + gnet_uri_set_scheme(uri,"http"); + this->uri = gnet_mms_helper(uri, 1); + } else { + this->proto = (uri->scheme) ? strdup(uri->scheme) : NULL; + this->connect_host = (uri->hostname) ? strdup(uri->hostname) : NULL; + this->connect_port = uri->port; + this->http_host = (uri->hostname) ? strdup(uri->hostname) : NULL; + this->http_port = uri->port; + this->proxy_user = NULL; + this->proxy_password = NULL; + this->host_user =(uri->user) ? strdup(uri->user) : NULL; + this->host_password = (uri->passwd) ? strdup(uri->passwd) : NULL; + this->uri = gnet_mms_helper(uri, 1); + } + + if(!this->uri) + goto fail; + + if (proxy_uri) { + gnet_uri_delete(proxy_uri); + proxy_uri = NULL; + } + if (uri) { + gnet_uri_delete(uri); + uri = NULL; + } + if (!mmsh_valid_proto(this->proto)) { + lprintf("mmsh: unsupported protocol\n"); + goto fail; + } + + if (!mmsh_connect_int(io, this, 0, 0)) + goto fail; + + return this; + +fail: + lprintf("mmsh: connect failed\n"); + if (proxy_uri) + gnet_uri_delete(proxy_uri); + if (uri) + gnet_uri_delete(uri); + if (this->s != -1) + close(this->s); + if (this->url) + free(this->url); + if (this->proxy_url) + free(this->proxy_url); + if (this->proto) + free(this->proto); + if (this->connect_host) + free(this->connect_host); + if (this->http_host) + free(this->http_host); + if (this->proxy_user) + free(this->proxy_user); + if (this->proxy_password) + free(this->proxy_password); + if (this->host_user) + free(this->host_user); + if (this->host_password) + free(this->host_password); + if (this->uri) + free(this->uri); + + free(this); + return NULL; +} + +static int get_media_packet (mms_io_t *io, mmsh_t *this) { + int ret, len = 0; + + if (get_chunk_header(io, this) == SUCCESS) { + switch (this->chunk_type) { + case CHUNK_TYPE_END: + /* this->chunk_seq_number: + * 0: stop + * 1: a new stream follows + */ + if (this->chunk_seq_number == 0) + return EOS; + + this->http_request_number = 1; + if (!mmsh_connect_int (io, this, 0, 0)) + return ERROR; + + /* What todo with: current_pos ?? + Also our chunk_seq_numbers will probably restart from 0! + If this happens with a seekable stream (does it ever?) + and we get a seek request after this were fscked! */ + this->seekable = 0; + + /* mmsh_connect_int reads the first data packet */ + /* this->buf_size is set by mmsh_connect_int */ + return GOT_HEADER_N_DATA; + + case CHUNK_TYPE_DATA: + /* nothing to do */ + break; + + case CHUNK_TYPE_RESET: + /* next chunk is an ASF header */ + + if (this->chunk_length != 0) { + /* that's strange, don't know what to do */ + lprintf("mmsh: non 0 sized reset chunk"); + return ERROR; + } + if ((ret = get_header (io, this)) != SUCCESS) { + lprintf("mmsh: failed to get header after reset chunk\n"); + return ret; + } + interp_header(io, this); + + /* What todo with: current_pos ?? + Also our chunk_seq_numbers might restart from 0! + If this happens with a seekable stream (does it ever?) + and we get a seek request after this were fscked! */ + this->seekable = 0; + + /* get_header reads the first data packet */ + /* this->buf_size is set by get_header */ + return GOT_HEADER_N_DATA; + + default: + lprintf("mmsh: unexpected chunk_type(0x%04x)\n", this->chunk_type); + return ERROR; + } + + len = io_read (io, this->s, this->buf, this->chunk_length); + + if (len == this->chunk_length) { + /* explicit padding with 0 */ + if (this->chunk_length > this->packet_length) { + lprintf("mmsh: chunk_length(%d) > packet_length(%d)\n", + this->chunk_length, this->packet_length); + return ERROR; + } + + memset(this->buf + this->chunk_length, 0, + this->packet_length - this->chunk_length); + this->buf_size = this->packet_length; + + return SUCCESS; + } else { + lprintf("mmsh: media packet read error, %d != %d\n", len, + this->chunk_length); + return ERROR; + } + } else if (ret == EOS) { + return EOS; + } else { + lprintf("mmsh: get_media_packet failed to get chunk header\n"); + return ret; + } +} + +int mmsh_peek_header (mmsh_t *this, char *data, int maxsize) { + int len; + + len = (this->asf_header_len < maxsize) ? this->asf_header_len : maxsize; + + memcpy(data, this->asf_header, len); + return len; +} + +int mmsh_read (mms_io_t *io, mmsh_t *this, char *data, int len) { + int total; + + total = 0; + + /* Check if the stream didn't get closed because of previous errors */ + if (this->s == -1) + return total; + + while (total < len) { + + if (this->asf_header_read < this->asf_header_len) { + int n, bytes_left ; + + bytes_left = this->asf_header_len - this->asf_header_read ; + + if ((len-total) < bytes_left) + n = len-total; + else + n = bytes_left; + + memcpy (&data[total], &this->asf_header[this->asf_header_read], n); + + this->asf_header_read += n; + total += n; + this->current_pos += n; + } else { + + int n, bytes_left ; + + bytes_left = this->buf_size - this->buf_read; + + if (bytes_left == 0) { + int ret; + + this->buf_size=this ->buf_read = 0; + ret = get_media_packet (io, this); + + switch (ret) { + case SUCCESS: + break; + case ERROR: + lprintf ("mmsh: get_media_packet failed\n"); + return total; + case EOS: + return total; + case GOT_HEADER_N_DATA: + continue; + } + bytes_left = this->buf_size; + } + + if ((len-total) < bytes_left) + n = len-total; + else + n = bytes_left; + + memcpy (&data[total], &this->buf[this->buf_read], n); + + this->buf_read += n; + total += n; + this->current_pos += n; + } + } + return total; +} + +off_t mmsh_seek (mms_io_t *io, mmsh_t *this, off_t offset, int origin) { + off_t dest; + off_t dest_packet_seq; + uint32_t orig_asf_header_len = this->asf_header_len; + uint32_t orig_asf_packet_len = this->packet_length; + + if (!this->seekable) + return this->current_pos; + + switch (origin) { + case SEEK_SET: + dest = offset; + break; + case SEEK_CUR: + dest = this->current_pos + offset; + break; + case SEEK_END: + dest = mmsh_get_length (this) + offset; + default: + return this->current_pos; + } + + dest_packet_seq = dest - this->asf_header_len; + dest_packet_seq = dest_packet_seq >= 0 ? + dest_packet_seq / this->packet_length : -1; + + if (dest_packet_seq < 0) { + if (this->chunk_seq_number > 0) { + lprintf("mmsh: seek within header, already read beyond first packet, resetting connection\n"); + if (!mmsh_connect_int(io, this, 0, 0)) { + /* Oops no more connection let our caller know things are fscked up */ + return this->current_pos = -1; + } + /* Some what simple / naive check to check for changed headers + if the header was changed we are once more fscked up */ + if (this->asf_header_len != orig_asf_header_len || + this->packet_length != orig_asf_packet_len) { + lprintf("mmsh: AIIEEE asf header or packet length changed on re-open for seek\n"); + /* Its a different stream, so its useless! */ + close (this->s); + this->s = -1; + return this->current_pos = -1; + } + } else + lprintf("mmsh: seek within header, resetting buf_read\n"); + + // reset buf_read + this->buf_read = 0; + this->asf_header_read = dest; + return this->current_pos = dest; + } + + // dest_packet_seq >= 0 + if (this->asf_num_packets && dest == this->asf_header_len + + this->asf_num_packets*this->packet_length) { + // Requesting the packet beyond the last packet, can cause the server to + // not return any packet or any eos command. This can cause + // mms_packet_seek() to hang. + // This is to allow seeking at end of stream, and avoid hanging. + --dest_packet_seq; + lprintf("mmsh: seek to eos!\n"); + } + + if (dest_packet_seq != this->chunk_seq_number) { + + if (this->asf_num_packets && dest_packet_seq >= this->asf_num_packets) { + // Do not seek beyond the last packet. + return this->current_pos; + } + + lprintf("mmsh: seek to %d, packet: %d\n", (int)dest, (int)dest_packet_seq); + if (!mmsh_connect_int(io, this, (dest_packet_seq+1) * this->packet_length, 0)) { + /* Oops no more connection let our caller know things are fscked up */ + return this->current_pos = -1; + } + /* Some what simple / naive check to check for changed headers + if the header was changed we are once more fscked up */ + if (this->asf_header_len != orig_asf_header_len || + this->packet_length != orig_asf_packet_len) { + lprintf("mmsh: AIIEEE asf header or packet length changed on re-open for seek\n"); + /* Its a different stream, so its useless! */ + close (this->s); + this->s = -1; + return this->current_pos = -1; + } + } + else + lprintf("mmsh: seek within current packet, dest: %d, current pos: %d\n", + (int)dest, (int)this->current_pos); + + /* make sure asf_header is seen as fully read by mmsh_read() this is needed + in case our caller tries to seek over part of the header, or when we've + done an actual packet seek as get_header() resets asf_header_read then. */ + this->asf_header_read = this->asf_header_len; + + /* check we got what we want */ + if (dest_packet_seq == this->chunk_seq_number) { + this->buf_read = dest - + (this->asf_header_len + dest_packet_seq*this->packet_length); + this->current_pos = dest; + } else { + lprintf("mmsh: Seek failed, wanted packet: %d, got packet: %d\n", + (int)dest_packet_seq, (int)this->chunk_seq_number); + this->buf_read = 0; + this->current_pos = this->asf_header_len + this->chunk_seq_number * + this->packet_length; + } + + lprintf("mmsh: current_pos after seek to %d: %d (buf_read %d)\n", + (int)dest, (int)this->current_pos, (int)this->buf_read); + + return this->current_pos; +} + +int mmsh_time_seek (mms_io_t *io, mmsh_t *this, double time_sec) { + uint32_t orig_asf_header_len = this->asf_header_len; + uint32_t orig_asf_packet_len = this->packet_length; + + if (!this->seekable) + return 0; + + lprintf("mmsh: time seek to %f secs\n", time_sec); + if (!mmsh_connect_int(io, this, 0, time_sec * 1000 + this->preroll)) { + /* Oops no more connection let our caller know things are fscked up */ + this->current_pos = -1; + return 0; + } + /* Some what simple / naive check to check for changed headers + if the header was changed we are once more fscked up */ + if (this->asf_header_len != orig_asf_header_len || + this->packet_length != orig_asf_packet_len) { + lprintf("mmsh: AIIEEE asf header or packet length changed on re-open for seek\n"); + /* Its a different stream, so its useless! */ + close (this->s); + this->s = -1; + this->current_pos = -1; + return 0; + } + + this->asf_header_read = this->asf_header_len; + this->buf_read = 0; + this->current_pos = this->asf_header_len + this->chunk_seq_number * + this->packet_length; + + lprintf("mmsh, current_pos after time_seek:%d\n", (int)this->current_pos); + + return 1; +} + +void mmsh_close (mmsh_t *this) { + if (this->s != -1) + close(this->s); + if (this->url) + free(this->url); + if (this->proxy_url) + free(this->proxy_url); + if (this->proto) + free(this->proto); + if (this->connect_host) + free(this->connect_host); + if (this->http_host) + free(this->http_host); + if (this->proxy_user) + free(this->proxy_user); + if (this->proxy_password) + free(this->proxy_password); + if (this->host_user) + free(this->host_user); + if (this->host_password) + free(this->host_password); + if (this->uri) + free(this->uri); + if (this) + free (this); +} + + +uint32_t mmsh_get_length (mmsh_t *this) { + /* we could / should return this->file_len here, but usually this->file_len + is longer then the calculation below, as usually an asf file contains an + asf index object after the data stream. However since we do not have a + (known) way to get to this index object through mms, we return a + calculated size of what we can get to when we know. */ + if (this->asf_num_packets) + return this->asf_header_len + this->asf_num_packets*this->packet_length; + else + return this->file_length; +} + +double mmsh_get_time_length (mmsh_t *this) { + return (double)(this->time_len) / 1e7; +} + +uint64_t mmsh_get_raw_time_length (mmsh_t *this) { + return this->time_len; +} + +off_t mmsh_get_current_pos (mmsh_t *this) { + return this->current_pos; +} + +uint32_t mmsh_get_asf_header_len (mmsh_t *this) { + return this->asf_header_len; +} + +uint32_t mmsh_get_asf_packet_len (mmsh_t *this) { + return this->packet_length; +} + +int mmsh_get_seekable (mmsh_t *this) { + return this->seekable; +} diff --git a/plugins/mms/libmms/mmsh.h b/plugins/mms/libmms/mmsh.h new file mode 100644 index 00000000..b222eeaa --- /dev/null +++ b/plugins/mms/libmms/mmsh.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2002-2003 the xine project + * + * This file is part of xine, a free video player. + * + * xine is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * xine is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + * $Id: mmsh.h,v 1.8 2007/12/11 20:24:48 jwrdegoede Exp $ + * + * libmmsh public header + */ + +#ifndef HAVE_MMSH_H +#define HAVE_MMSH_H + +#include <inttypes.h> +#include <stdio.h> +#include <sys/types.h> +#include "mmsio.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef struct mmsh_s mmsh_t; + +char* mmsh_connect_common(int *s ,int *port, char *url, char **host, char **path, char **file); +mmsh_t* mmsh_connect (mms_io_t *io, void *data, const char *url_, int bandwidth); + +int mmsh_read (mms_io_t *io, mmsh_t *instance, char *data, int len); +int mmsh_time_seek (mms_io_t *io, mmsh_t *instance, double time_sec); +mms_off_t mmsh_seek (mms_io_t *io, mmsh_t *instance, mms_off_t offset, int origin); +uint32_t mmsh_get_length (mmsh_t *instance); +double mmsh_get_time_length (mmsh_t *instance); +uint64_t mmsh_get_raw_time_length (mmsh_t *instance); +mms_off_t mmsh_get_current_pos (mmsh_t *instance); +void mmsh_close (mmsh_t *instance); + +int mmsh_peek_header (mmsh_t *instance, char *data, int maxsize); + +uint32_t mmsh_get_asf_header_len (mmsh_t *instance); + +uint32_t mmsh_get_asf_packet_len (mmsh_t *instance); + +int mmsh_get_seekable (mmsh_t *instance); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif diff --git a/plugins/mms/libmms/mmsio.h b/plugins/mms/libmms/mmsio.h new file mode 100644 index 00000000..8e4304d9 --- /dev/null +++ b/plugins/mms/libmms/mmsio.h @@ -0,0 +1,93 @@ +#ifndef __MMS_IO_H__ +#define __MMS_IO_H__ + +#define LIBMMS_HAVE_64BIT_OFF_T 1 + +/* On 64 bit file offset capable systems, libmms' configure script adds + -D_FILE_OFFSET_BITS=64 to the CFLAGS. This causes off_t to be 64 bit, + When an app which includes this header file gets compiled without + -D_FILE_OFFSET_BITS=64, it should still expect / pass 64 bit ints for + off_t, this acomplishes this: */ +#if defined LIBMMS_HAVE_64BIT_OFF_T && !defined __MMS_C__ +#define mms_off_t int64_t +#else +#define mms_off_t off_t +#endif + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef mms_off_t (*mms_io_write_func)(void *data, int socket, char *buf, mms_off_t num); +typedef mms_off_t (*mms_io_read_func)(void *data, int socket, char *buf, mms_off_t num); + +/* select states */ +#define MMS_IO_READ_READY 1 +#define MMS_IO_WRITE_READY 2 + +enum + { + MMS_IO_STATUS_READY, /* IO can be safely performed */ + MMS_IO_STATUS_ERROR, /* There was IO error */ + MMS_IO_STATUS_ABORTED, /* IO command was (somehow) + aborted. This is not error, but invalidates IO for further operations*/ + MMS_IO_STATUS_TIMEOUT /* Timeout was exceeded */ + }; + +/* + * Waits for a file descriptor/socket to change status. + * + * users can use this handler to provide their own implementations, + * for example abortable ones + * + * params : + * data whatever parameter may be needed by implementation + * fd file/socket descriptor + * state MMS_IO_READ_READY, MMS_IO_WRITE_READY + * timeout_sec timeout in seconds + * + * + * return value : + * MMS_IO_READY the file descriptor is ready for cmd + * MMS_IO_ERROR an i/o error occured + * MMS_IO_ABORTED command aborted + * MMS_IO_TIMEOUT the file descriptor is not ready after timeout_msec milliseconds + * every other return value is interpreted same as MMS_IO_ABORTED + */ +typedef int (*mms_io_select_func)(void *data, int fd, int state, int timeout_msec); + +/* + * open a tcp connection + * + * params : + * stream needed for reporting errors but may be NULL + * host address of target + * port port on target + * + * returns a socket descriptor or -1 if an error occured + */ +typedef int (*mms_io_tcp_connect_func)(void *data, const char *host, int port); + +typedef struct +{ + mms_io_select_func select; + void *select_data; + mms_io_read_func read; + void *read_data; + mms_io_write_func write; + void *write_data; + mms_io_tcp_connect_func connect; + void *connect_data; +} mms_io_t; + +/* set default IO implementation, it will be used in absence of specific IO + parameter. Structure is referenced, not copied, must remain valid for entire + usage period. Passing NULL reverts to default, POSIX based implementation */ +void mms_set_default_io_impl(const mms_io_t *io); +const mms_io_t* mms_get_default_io_impl(); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __MMS_IO_H__ */ diff --git a/plugins/mms/libmms/mmsx.c b/plugins/mms/libmms/mmsx.c new file mode 100644 index 00000000..fc95de86 --- /dev/null +++ b/plugins/mms/libmms/mmsx.c @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2007 Hans de Goede <j.w.r.degoede@hhs.nl> + * + * This file is part of libmms a free mms protocol library + * + * libmms is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * libmss is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +/* + * mmsx is a small wrapper around the mms and mmsh protocol implementations + * in libmms. The mmsx functions provide transparent access to both protocols + * so that programs who wish to support both can do so with a single code path + * if desired. + */ + +#include <stdlib.h> +#include "mmsx.h" +#include "mms.h" +#include "mmsh.h" + +struct mmsx_s { + mms_t *connection; + mmsh_t *connection_h; +}; + +mmsx_t *mmsx_connect(mms_io_t *io, void *data, const char *url, int bandwidth) +{ + mmsx_t *mmsx = calloc(1, sizeof(mmsx_t)); + char *try_mms_first = getenv("LIBMMS_TRY_MMS_FIRST"); + + if (!mmsx) + return mmsx; + + /* Normally we try mmsh first, as mms: is a rollover protocol identifier + according to microsoft and recent mediaplayer versions will try + mmsh before mms for mms:// uris. Note that in case of a mmst:// or a + mmsh:// url the mms[h]_connect function will directly exit if it cannot + handle it. The LIBMMS_TRY_MMS_FIRST environment variable is there for + testing the mms code against servers which accept both mmsh and mms. */ + if (try_mms_first, 1) { + mmsx->connection = mms_connect(io, data, url, bandwidth); + if (mmsx->connection) + return mmsx; + } + + mmsx->connection_h = mmsh_connect(io, data, url, bandwidth); + if (mmsx->connection_h) + return mmsx; + + if (!try_mms_first, 0) { + mmsx->connection = mms_connect(io, data, url, bandwidth); + if (mmsx->connection) + return mmsx; + } + + free(mmsx); + return NULL; +} + +int mmsx_read (mms_io_t *io, mmsx_t *mmsx, char *data, int len) +{ + if(mmsx->connection) + return mms_read(io, mmsx->connection, data, len); + else + return mmsh_read(io, mmsx->connection_h, data, len); +} + +int mmsx_time_seek (mms_io_t *io, mmsx_t *mmsx, double time_sec) +{ + if(mmsx->connection) + return mms_time_seek(io, mmsx->connection, time_sec); + else + return mmsh_time_seek(io, mmsx->connection_h, time_sec); +} + +mms_off_t mmsx_seek (mms_io_t *io, mmsx_t *mmsx, mms_off_t offset, int origin) +{ + if(mmsx->connection) + return mms_seek(io, mmsx->connection, offset, origin); + else + return mmsh_seek(io, mmsx->connection_h, offset, origin); +} + +double mmsx_get_time_length (mmsx_t *mmsx) +{ + if(mmsx->connection) + return mms_get_time_length(mmsx->connection); + else + return mmsh_get_time_length(mmsx->connection_h); +} + +uint64_t mmsx_get_raw_time_length (mmsx_t *mmsx) +{ + if(mmsx->connection) + return mms_get_raw_time_length(mmsx->connection); + else + return mmsh_get_raw_time_length(mmsx->connection_h); +} + +uint32_t mmsx_get_length (mmsx_t *mmsx) +{ + if(mmsx->connection) + return mms_get_length(mmsx->connection); + else + return mmsh_get_length(mmsx->connection_h); +} + +void mmsx_close (mmsx_t *mmsx) +{ + if(mmsx->connection) + mms_close(mmsx->connection); + else + mmsh_close(mmsx->connection_h); + + free(mmsx); +} + +int mmsx_peek_header (mmsx_t *mmsx, char *data, int maxsize) +{ + if(mmsx->connection) + return mms_peek_header(mmsx->connection, data, maxsize); + else + return mmsh_peek_header(mmsx->connection_h, data, maxsize); +} + +mms_off_t mmsx_get_current_pos (mmsx_t *mmsx) +{ + if(mmsx->connection) + return mms_get_current_pos(mmsx->connection); + else + return mmsh_get_current_pos(mmsx->connection_h); +} + +uint32_t mmsx_get_asf_header_len(mmsx_t *mmsx) +{ + if(mmsx->connection) + return mms_get_asf_header_len(mmsx->connection); + else + return mmsh_get_asf_header_len(mmsx->connection_h); +} + +uint64_t mmsx_get_asf_packet_len (mmsx_t *mmsx) +{ + if(mmsx->connection) + return mms_get_asf_packet_len(mmsx->connection); + else + return mmsh_get_asf_packet_len(mmsx->connection_h); +} + +int mmsx_get_seekable (mmsx_t *mmsx) +{ + if(mmsx->connection) + return mms_get_seekable(mmsx->connection); + else + return mmsh_get_seekable(mmsx->connection_h); +} diff --git a/plugins/mms/libmms/mmsx.h b/plugins/mms/libmms/mmsx.h new file mode 100644 index 00000000..df4adfce --- /dev/null +++ b/plugins/mms/libmms/mmsx.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2007 Hans de Goede <j.w.r.degoede@hhs.nl> + * + * This file is part of libmms a free mms protocol library + * + * libmms is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * libmss is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + * libmms public header + */ + +/* + * mmsx is a small wrapper around the mms and mmsh protocol implementations + * in libmms. The mmsx functions provide transparent access to both protocols + * so that programs who wish to support both can do so with a single code path + * if desired. + */ + +#ifndef HAVE_MMSX_H +#define HAVE_MMSX_H + +#include <inttypes.h> +#include "mmsio.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef struct mmsx_s mmsx_t; + +mmsx_t* mmsx_connect (mms_io_t *io, void *data, const char *url, int bandwidth); + +int mmsx_read (mms_io_t *io, mmsx_t *instance, char *data, int len); +int mmsx_time_seek (mms_io_t *io, mmsx_t *instance, double time_sec); +mms_off_t mmsx_seek (mms_io_t *io, mmsx_t *instance, mms_off_t offset, int origin); +/* return total playback time in seconds */ +double mmsx_get_time_length (mmsx_t *instance); +/* return raw total playback time in 100 nanosecs (10^-7) */ +uint64_t mmsx_get_raw_time_length (mmsx_t *instance); +uint32_t mmsx_get_length (mmsx_t *instance); +void mmsx_close (mmsx_t *instance); + +int mmsx_peek_header (mmsx_t *instance, char *data, int maxsize); + +mms_off_t mmsx_get_current_pos (mmsx_t *instance); + +uint32_t mmsx_get_asf_header_len (mmsx_t *instance); + +uint64_t mmsx_get_asf_packet_len (mmsx_t *instance); + +int mmsx_get_seekable (mmsx_t *instance); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif + diff --git a/plugins/mms/libmms/uri.c b/plugins/mms/libmms/uri.c new file mode 100644 index 00000000..6b6475eb --- /dev/null +++ b/plugins/mms/libmms/uri.c @@ -0,0 +1,1033 @@ +/* GNet - Networking library + * Copyright (C) 2000-2003 David Helder, David Bolcsfoldi, Eric Williams + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* FIXME: #include "gnet-private.h" */ +#include <string.h> +#include <stdlib.h> +#include <ctype.h> +#include <stdio.h> + +#include "uri.h" +#include <string.h> + +static void field_unescape (char *str); +static char* field_escape (char* str, unsigned char mask); + +#define USERINFO_ESCAPE_MASK 0x01 +#define PATH_ESCAPE_MASK 0x02 +#define QUERY_ESCAPE_MASK 0x04 +#define FRAGMENT_ESCAPE_MASK 0x08 + +/* #define FALSE 0 */ +/* #define TRUE (!FALSE) */ + +static unsigned char neednt_escape_table[] = +{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0f, 0x00, 0x00, 0x0f, 0x00, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0e, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0x0f, 0x00, 0x0c, + 0x0e, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x00, 0x0f, 0x00, 0x00, 0x0f, + 0x00, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x0f, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + + +/* +Perl code to generate above table: + +#!/usr/bin/perl + +$ok = "abcdefghijklmnopqrstuvwxyz" . + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" . + "0123456789" . + "-_.!~*'()"; +$userinfo_ok = ';:&=+\$,'; +$path_ok = ':\@&=+\$,;/'; +$query_ok = ';/?:\@&=+\$,'; +$fragment_ok = ';/?:\@&=+\$,'; + +for ($i = 0; $i < 32; $i++) +{ + print " "; + for ($j = 0; $j < 8; $j++) + { + $num = 0; + $letter = chr(($i * 8) + $j); + + $num |= 0b0001 if (index($userinfo_ok, $letter) != -1); + $num |= 0b0010 if (index($path_ok, $letter) != -1); + $num |= 0b0100 if (index($query_ok, $letter) != -1); + $num |= 0b1000 if (index($fragment_ok, $letter) != -1); + $num |= 0b1111 if (index($ok, $letter) != -1); + + printf "0x%02x, ", $num; + } + print "\n"; +} +*/ + + +/* our own ISSPACE. ANSI isspace is local dependent */ +#define ISSPACE(C) (((C) >= 9 && (C) <= 13) || (C) == ' ') + + +static int split_user_passwd(const char* in, char** user, char** passwd) +{ + char *tmp = strdup(in); + + if(!tmp) + return 0; + *passwd = strchr(tmp, ':'); + if(!(*passwd)) + { + free(tmp); + return 0; + } + *((*passwd)++) = '\0'; // don't you love C? :) + + *user = strdup(tmp); + if(!*user) + return 0; + *passwd = strdup(*passwd); + if(!*passwd) + return 0; + + free(tmp); + return 1; +} + +/** + * gnet_uri_new + * @uri: URI string + * + * Creates a #GURI from a string. Empty fields are set to NULL. The + * parser does not validate the URI -- it will accept some malformed + * URI. URIs are usually in the form + * scheme://userinfo@hostname:port/path?query#fragment + * + * URIs created from user input are typically unescaped. URIs + * created from machine input (e.g. received over the internet) are + * typically escaped. + * + * Returns: a new #GURI, or NULL if there was a failure. + * + **/ +GURI* +gnet_uri_new (const char* uri) +{ + GURI* guri = NULL; + const char* p; + const char* temp; + + if (!uri) { + return NULL; + } + + /* Skip initial whitespace */ + p = uri; + while (*p && ISSPACE((int)*p)) + ++p; + if (!*p) /* Error if it's just a string of space */ + return NULL; + + guri = malloc (sizeof (GURI)); + memset (guri, 0, sizeof (GURI)); + + /* Scheme */ + temp = p; + while (*p && *p != ':' && *p != '/' && *p != '?' && *p != '#') + ++p; + if (*p == ':') + { + guri->scheme = strndup (temp, p - temp); + ++p; + } + else /* This char is NUL, /, ?, or # */ + p = temp; + + /* Authority */ + if (*p == '/' && p[1] == '/') + { + char *userinfo; + p += 2; + + /* Userinfo */ + temp = p; + while (*p && *p != '@' && *p != '/' ) /* Look for @ or / */ + ++p; + if (*p == '@') /* Found userinfo */ + { + userinfo = strndup (temp, p - temp); + if(!split_user_passwd(userinfo, &guri->user, &guri->passwd)) + { + free(userinfo); + goto error; + } + free(userinfo); + ++p; + } + else + p = temp; + + /* Hostname */ + + /* Check for IPv6 canonical hostname in brackets */ + if (*p == '[') + { + p++; /* Skip [ */ + temp = p; + while (*p && *p != ']') ++p; + if ((p - temp) == 0) + goto error; + guri->hostname = strndup (temp, p - temp); + if (*p) + p++; /* Skip ] (if there) */ + } + else + { + temp = p; + while (*p && *p != '/' && *p != '?' && *p != '#' && *p != ':') ++p; + if ((p - temp) == 0) + goto error; + guri->hostname = strndup (temp, p - temp); + } + + /* Port */ + if (*p == ':') + { + for (++p; isdigit((int)*p); ++p) + guri->port = guri->port * 10 + (*p - '0'); + } + + } + + /* Path (we are liberal and won't check if it starts with /) */ + temp = p; + while (*p && *p != '?' && *p != '#') + ++p; + if (p != temp) + guri->path = strndup(temp, p - temp); + + /* Query */ + if (*p == '?') + { + temp = p + 1; + while (*p && *p != '#') + ++p; + guri->query = strndup (temp, p - temp); + } + + /* Fragment */ + if (*p == '#') + { + ++p; + guri->fragment = strdup (p); + } + + return guri; + + error: + gnet_uri_delete (guri); + return NULL; +} + + +/** + * gnet_uri_new_fields + * @scheme: scheme + * @hostname: host name + * @port: port + * @path: path + * + * Creates a #GURI from the fields. This function uses the most + * common fields. Use gnet_uri_new_fields_all() to specify all + * fields. + * + * Returns: a new #GURI. + * + **/ +GURI* +gnet_uri_new_fields (const char* scheme, const char* hostname, + const int port, const char* path) +{ + GURI* uri = NULL; + + uri = malloc (sizeof (GURI)); + memset (uri, 0, sizeof (GURI)); + if (scheme) uri->scheme = strdup (scheme); + if (hostname) uri->hostname = strdup (hostname); + uri->port = port; + if (path) uri->path = strdup (path); + + return uri; +} + + +/** + * gnet_uri_new_fields_all + * @scheme: scheme + * @userinfo: user info + * @hostname: host name + * @port: port + * @path: path + * @query: query + * @fragment: fragment + * + * Creates a #GURI from all fields. + * + * Returns: a new #GURI. + * + **/ +GURI* +gnet_uri_new_fields_all (const char* scheme, const char* user, + const char* passwd, const char* hostname, + const int port, const char* path, + const char* query, const char* fragment) +{ + GURI* uri = NULL; + + uri = malloc (sizeof (GURI)); + memset (uri, 0, sizeof (GURI)); + if (scheme) uri->scheme = strdup (scheme); + if (user) uri->user = strdup (user); + if (passwd) uri->passwd = strdup (passwd); + if (hostname) uri->hostname = strdup (hostname); + uri->port = port; + if (path) uri->path = strdup (path); + if (query) uri->query = strdup (query); + if (fragment) uri->fragment = strdup (fragment); + + return uri; +} + + +/** + * gnet_uri_clone: + * @uri: a #GURI + * + * Copies a #GURI. + * + * Returns: a copy of @uri. + * + **/ +GURI* +gnet_uri_clone (const GURI* uri) +{ + GURI* uri2; + + if (!uri) { + return NULL; + } + + uri2 = malloc (sizeof (GURI)); + memset (uri2, 0, sizeof (GURI)); + uri2->scheme = strdup (uri->scheme); + uri2->user = strdup (uri->user); + uri2->passwd = strdup (uri->passwd); + uri2->hostname = strdup (uri->hostname); + uri2->port = uri->port; + uri2->path = strdup (uri->path); + uri2->query = strdup (uri->query); + uri2->fragment = strdup (uri->fragment); + + return uri2; +} + + +/** + * gnet_uri_delete: + * @uri: a #GURI + * + * Deletes a #GURI. + * + **/ +void +gnet_uri_delete (GURI* uri) +{ + if (uri) + { + free (uri->scheme); + free (uri->user); + free (uri->passwd); + free (uri->hostname); + free (uri->path); + free (uri->query); + free (uri->fragment); + free (uri); + } +} + + + + +#define SAFESTRCMP(A,B) (((A)&&(B))?(strcmp((A),(B))):((A)||(B))) + +/** + * gnet_uri_equal + * @p1: a #GURI + * @p2: another #GURI + * + * Compares two #GURI's for equality. + * + * Returns: TRUE if they are equal; FALSE otherwise. + * + **/ +int +gnet_uri_equal (const char * p1, const char * p2) +{ + const GURI* uri1 = (const GURI*) p1; + const GURI* uri2 = (const GURI*) p2; + + if (!uri1) { + return 0; + } + if (!uri2) { + return 0; + } + + if (uri1->port == uri2->port && + !SAFESTRCMP(uri1->scheme, uri2->scheme) && + !SAFESTRCMP(uri1->user, uri2->user) && + !SAFESTRCMP(uri1->passwd, uri2->passwd) && + !SAFESTRCMP(uri1->hostname, uri2->hostname) && + !SAFESTRCMP(uri1->path, uri2->path) && + !SAFESTRCMP(uri1->query, uri2->query) && + !SAFESTRCMP(uri1->fragment, uri2->fragment)) + return 1; + + return 0; +} + + +/** + * gnet_uri_hash + * @p: a #GURI + * + * Creates a hash code for @p for use with GHashTable. + * + * Returns: hash code for @p. + * + **/ +#if 0 +unsigned int +gnet_uri_hash (const char * p) +{ + const GURI* uri = (const GURI*) p; + unsigned int h = 0; + + if (!uri) { + return 0; + } + + if (uri->scheme) h = g_str_hash (uri->scheme); + if (uri->user) h ^= g_str_hash (uri->user); + if (uri->passwd) h ^= g_str_hash (uri->passwd); + if (uri->hostname) h ^= g_str_hash (uri->hostname); + h ^= uri->port; + if (uri->path) h ^= g_str_hash (uri->path); + if (uri->query) h ^= g_str_hash (uri->query); + if (uri->fragment) h ^= g_str_hash (uri->fragment); + + return h; +} +#endif + + +/** + * gnet_uri_escape + * @uri: a #GURI + * + * Escapes the fields in a #GURI. Network protocols use escaped + * URIs. People use unescaped URIs. + * + **/ +void +gnet_uri_escape (GURI* uri) +{ + if (!uri) { + return; + } + + uri->user = field_escape (uri->user, USERINFO_ESCAPE_MASK); + uri->passwd = field_escape (uri->passwd, USERINFO_ESCAPE_MASK); + uri->path = field_escape (uri->path, PATH_ESCAPE_MASK); + uri->query = field_escape (uri->query, QUERY_ESCAPE_MASK); + uri->fragment = field_escape (uri->fragment, FRAGMENT_ESCAPE_MASK); +} + + +/** + * gnet_uri_unescape + * @uri: a #GURI + * + * Unescapes the fields in the URI. Network protocols use escaped + * URIs. People use unescaped URIs. + * + **/ +void +gnet_uri_unescape (GURI* uri) +{ + if (!uri) { + return; + } + + if (uri->user) + field_unescape (uri->user); + if (uri->passwd) + field_unescape (uri->passwd); + if (uri->path) + field_unescape (uri->path); + if (uri->query) + field_unescape (uri->query); + if (uri->fragment) + field_unescape (uri->fragment); +} + + +static char* +field_escape (char* str, unsigned char mask) +{ + int len; + int i; + int must_escape = 0; + char* dst; + int j; + + if (str == NULL) + return NULL; + + /* Roughly calculate buffer size */ + len = 0; + for (i = 0; str[i]; i++) + { + if (neednt_escape_table[(unsigned int) str[i]] & mask) + len++; + else + { + len += 3; + must_escape = 1; + } + } + + /* Don't escape if unnecessary */ + if (must_escape == 0) + return str; + + /* Allocate buffer */ + dst = (char*) malloc(len + 1); + memset (dst, 0, len+1); + + /* Copy */ + for (i = j = 0; str[i]; i++, j++) + { + /* Unescaped character */ + if (neednt_escape_table[(unsigned int) str[i]] & mask) + { + dst[j] = str[i]; + } + + /* Escaped character */ + else + { + dst[j] = '%'; + + if (((str[i] & 0xf0) >> 4) < 10) + dst[j+1] = ((str[i] & 0xf0) >> 4) + '0'; + else + dst[j+1] = ((str[i] & 0xf0) >> 4) + 'a' - 10; + + if ((str[i] & 0x0f) < 10) + dst[j+2] = (str[i] & 0x0f) + '0'; + else + dst[j+2] = (str[i] & 0x0f) + 'a' - 10; + + j += 2; /* and j is incremented in loop too */ + } + } + dst[j] = '\0'; + + free (str); + return dst; +} + + + +static void +field_unescape (char* s) +{ + char* src; + char* dst; + + for (src = dst = s; *src; ++src, ++dst) + { + if (src[0] == '%' && src[1] != '\0' && src[2] != '\0') + { + int high, low; + + if ('a' <= src[1] && src[1] <= 'f') + high = src[1] - 'a' + 10; + else if ('A' <= src[1] && src[1] <= 'F') + high = src[1] - 'A' + 10; + else if ('0' <= src[1] && src[1] <= '9') + high = src[1] - '0'; + else /* malformed */ + goto regular_copy; + + if ('a' <= src[2] && src[2] <= 'f') + low = src[2] - 'a' + 10; + else if ('A' <= src[2] && src[2] <= 'F') + low = src[2] - 'A' + 10; + else if ('0' <= src[2] && src[2] <= '9') + low = src[2] - '0'; + else /* malformed */ + goto regular_copy; + + *dst = (char)((high << 4) + low); + src += 2; + } + else + { + regular_copy: + *dst = *src; + } + } + + *dst = '\0'; +} + + + +/** + * gnet_uri_get_string + * @uri: a #GURI + * + * Gets a string representation of a #GURI. This function does not + * escape or unescape the fields first. Call gnet_uri_escape() or + * gnet_uri_unescape first if necessary. + * + * Returns: a string. + * + **/ +char* +gnet_uri_get_string (const GURI* uri) +{ + char* rv = NULL; + char *buffer = malloc (1024); + memset (buffer, 0, 1024); + char *b = buffer; + int remaining = 1024; + + if (!uri) { + return NULL; + } + + if (uri->scheme) { + int n = snprintf (buffer, n, "%s:", uri->scheme); + buffer += n; + remaining -= n; + if (remaining < 10) { + free (buffer); + return NULL; + } + } + + if (uri->user || uri->passwd || uri->hostname || uri->port) { + strcpy (buffer, "//"); + buffer += 2; + remaining -= 2; + if (remaining < 10) { + free (buffer); + return NULL; + } + } + + if (uri->user) + { + int n = strlen (uri->user); + memcpy (buffer, uri->user, n+1); + buffer += n; + remaining -= n; + if (remaining < 10) { + free (buffer); + return NULL; + } + strcpy (buffer, "@"); + buffer += 1; + remaining -= 1; + if (remaining < 10) { + free (buffer); + return NULL; + } + } + if (uri->passwd) + { + int n = strlen (uri->passwd); + memcpy (buffer, uri->passwd, n+1); + buffer += n; + remaining -= n; + if (remaining < 10) { + free (buffer); + return NULL; + } + strcpy (buffer, "@"); + buffer += 1; + remaining -= 1; + if (remaining < 10) { + free (buffer); + return NULL; + } + } + + /* Add brackets around the hostname if it's IPv6 */ + if (uri->hostname) + { + if (strchr(uri->hostname, ':') == NULL) { + int n = strlen (uri->hostname); + memcpy (buffer, uri->hostname, n+1); + buffer += n; + remaining -= n; + if (remaining < 10) { + free (buffer); + return NULL; + } + } + else { + int n = snprintf (buffer, remaining, "[%s]", uri->hostname); + buffer += n; + remaining -= n; + if (remaining < 10) { + free (buffer); + return NULL; + } + } + } + + if (uri->port) { + int n = snprintf (buffer, remaining, ":%d", uri->port); + buffer += n; + remaining -= n; + if (remaining < 10) { + free (buffer); + return NULL; + } + } + + if (uri->path) + { + if (*uri->path == '/' || + !(uri->user || uri->passwd || uri->hostname || uri->port)) { + int n = strlen (uri->path); + memcpy (buffer, uri->path, n+1); + buffer += n; + remaining -= n; + if (remaining < 10) { + free (buffer); + return NULL; + } + } + else { + int n = snprintf (buffer, remaining, "/%s", uri->path); + buffer += n; + remaining -= n; + if (remaining < 10) { + free (buffer); + return NULL; + } + } + } + + if (uri->query) { + int n = snprintf (buffer, remaining, "?%s", uri->query); + buffer += n; + remaining -= n; + if (remaining < 10) { + free (buffer); + return NULL; + } + } + + if (uri->fragment) { + int n = snprintf (buffer, remaining, "#%s", uri->fragment); + buffer += n; + remaining -= n; + if (remaining < 10) { + free (buffer); + return NULL; + } + } + + /* Free only GString not data contained, return the data instead */ + return b; +} + + +/** + * gnet_uri_set_scheme + * @uri: a #GURI + * @scheme: scheme + * + * Sets a #GURI's scheme. + * + **/ +void +gnet_uri_set_scheme (GURI* uri, const char* scheme) +{ + if (!uri) { + return; + } + + if (uri->scheme) + { + free (uri->scheme); + uri->scheme = NULL; + } + + if (scheme) + uri->scheme = strdup (scheme); +} + + +/** + * gnet_uri_set_userinfo + * @uri: a #GURI + * @userinfo: user info + * + * Sets a #GURI's user info. + * + **/ +void +gnet_uri_set_userinfo (GURI* uri, const char* user, const char* passwd) +{ + if (!uri) { + return; + } + + if (uri->user) + { + free (uri->user); + uri->user = NULL; + } + if (uri->passwd) + { + free (uri->passwd); + uri->passwd = NULL; + } + + if (user) + uri->user = strdup (user); + if (passwd) + uri->passwd = strdup (passwd); +} + + +/** + * gnet_uri_set_hostname + * @uri: a #GURI + * @hostname: host name + * + * Sets a #GURI's host name. + * + **/ +void +gnet_uri_set_hostname (GURI* uri, const char* hostname) +{ + if (!uri) { + return; + } + + if (uri->hostname) + { + free (uri->hostname); + uri->hostname = NULL; + } + + if (hostname) + uri->hostname = strdup (hostname); +} + + +/** + * gnet_uri_set_port + * @uri: a #GURI + * @port: port + * + * Set a #GURI's port. + * + **/ +void +gnet_uri_set_port (GURI* uri, int port) +{ + uri->port = port; +} + + +/** + * gnet_uri_set_path + * @uri: a #GURI + * @path: path + * + * Set a #GURI's path. + * + **/ +void +gnet_uri_set_path (GURI* uri, const char* path) +{ + if (!uri) { + return; + } + + if (uri->path) + { + free (uri->path); + uri->path = NULL; + } + + if (path) + uri->path = strdup (path); +} + + + +/** + * gnet_uri_set_query + * @uri: a #GURI + * @query: query + * + * Set a #GURI's query. + * + **/ +void +gnet_uri_set_query (GURI* uri, const char* query) +{ + if (!uri) { + return; + } + + if (uri->query) + { + free (uri->query); + uri->query = NULL; + } + + if (query) + uri->query = strdup (query); +} + + +/** + * gnet_uri_set_fragment + * @uri: a #GURI + * @fragment: fragment + * + * Set a #GURI's fragment. + * + **/ +void +gnet_uri_set_fragment (GURI* uri, const char* fragment) +{ + if (!uri) { + return; + } + + if (uri->fragment) + { + free (uri->fragment); + uri->fragment = NULL; + } + + if (fragment) + uri->fragment = strdup (fragment); +} + + +/** + * gnet_mms_helper + * @uri: a #GURI + * + * returns char* representation of an uri that is sutable for + * using in mms protocol. + * '/path?query' + * + **/ + +char* gnet_mms_helper(const GURI* uri, int make_absolute) +{ + size_t len = 0; + char *ret, *tmp = NULL; + + + /* Strip leading slashes and calculate the length of the path + * which might not be present in the URI */ + if (uri->path) { + tmp = uri->path; + while (*tmp == '/') + ++tmp; + len += strlen(tmp); + } + /* Append length of the query part */ + if (uri->query) + len += strlen(uri->query) + 1; /* + '?' */ + + if (!(ret = (char *) malloc(len + 2))) + return NULL; + memset (ret, 0, len + 2); + + if (make_absolute) + strcpy(ret, "/"); + else + ret[0] = 0; + + /* Copy the optional path */ + if (tmp) + strcat(ret, tmp); + + /* Copy the optional query */ + if (uri->query) { + strcat(ret, "?"); + strcat(ret, uri->query); + } + + return ret; +} diff --git a/plugins/mms/libmms/uri.h b/plugins/mms/libmms/uri.h new file mode 100644 index 00000000..75c96779 --- /dev/null +++ b/plugins/mms/libmms/uri.h @@ -0,0 +1,92 @@ +/* GNet - Networking library + * Copyright (C) 2000-2001 David Helder, David Bolcsfoldi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +#ifndef _GNET_URI_H +#define _GNET_URI_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/** + * GURI: + * @scheme: Scheme (or protocol) + * @userinfo: User info + * @hostname: Host name + * @port: Port number + * @path: Path + * @query: Query + * @fragment: Fragment + * + * The #GURI structure represents a URI. All fields in this + * structure are publicly readable. + * + **/ +typedef struct _GURI GURI; + +struct _GURI +{ + char* scheme; + char* user; + char* passwd; + char* hostname; + int port; + char* path; + char* query; + char* fragment; +}; + + + +GURI* gnet_uri_new (const char* uri); +GURI* gnet_uri_new_fields (const char* scheme, const char* hostname, + const int port, const char* path); +GURI* +gnet_uri_new_fields_all (const char* scheme, const char* user, + const char* passwd, const char* hostname, + const int port, const char* path, + const char* query, const char* fragment); +GURI* gnet_uri_clone (const GURI* uri); +void gnet_uri_delete (GURI* uri); + +int gnet_uri_equal (const char * p1, const char * p2); +unsigned int gnet_uri_hash (const char * p); + +void gnet_uri_escape (GURI* uri); +void gnet_uri_unescape (GURI* uri); + +char* gnet_uri_get_string (const GURI* uri); + +void gnet_uri_set_scheme (GURI* uri, const char* scheme); +void gnet_uri_set_userinfo (GURI* uri, const char* user, const char* passwd); +void gnet_uri_set_hostname (GURI* uri, const char* hostname); +void gnet_uri_set_port (GURI* uri, int port); +void gnet_uri_set_path (GURI* uri, const char* path); +void gnet_uri_set_query (GURI* uri, const char* query); +void gnet_uri_set_fragment (GURI* uri, const char* fragment); + +char* gnet_mms_helper(const GURI* uri, int make_absolute); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* _GNET_URI_H */ diff --git a/plugins/mms/mms.c b/plugins/mms/mmsplug.c index 4ad57832..4ad57832 100644 --- a/plugins/mms/mms.c +++ b/plugins/mms/mmsplug.c diff --git a/plugins/mpgmad/mpgmad.c b/plugins/mpgmad/mpgmad.c index 098d672a..13544238 100644 --- a/plugins/mpgmad/mpgmad.c +++ b/plugins/mpgmad/mpgmad.c @@ -372,108 +372,110 @@ cmp3_scan_stream (buffer_t *buffer, int sample) { if (sample <= 0 && !got_xing_header) { size_t framepos = deadbeef->ftell (buffer->file); -// trace ("trying to read xing header at pos %d\n", framepos); - if (ver == 1) { - deadbeef->fseek (buffer->file, 32, SEEK_CUR); - } - else { - deadbeef->fseek (buffer->file, 17, SEEK_CUR); - } - const char xing[] = "Xing"; - const char info[] = "Info"; - char magic[4]; - if (deadbeef->fread (magic, 1, 4, buffer->file) != 4) { - trace ("cmp3_scan_stream: EOF while checking for Xing header\n"); - return -1; // EOF - } - -// trace ("xing magic: %c%c%c%c\n", magic[0], magic[1], magic[2], magic[3]); - - if (!strncmp (xing, magic, 4) || !strncmp (info, magic, 4)) { - trace ("xing/info frame found\n"); - buffer->startoffset += packetlength; - // read flags - uint32_t flags; - uint8_t buf[4]; - if (deadbeef->fread (buf, 1, 4, buffer->file) != 4) { - trace ("cmp3_scan_stream: EOF while parsing Xing header\n"); + if (!buffer->file->vfs->streaming) { + // trace ("trying to read xing header at pos %d\n", framepos); + if (ver == 1) { + deadbeef->fseek (buffer->file, 32, SEEK_CUR); + } + else { + deadbeef->fseek (buffer->file, 17, SEEK_CUR); + } + const char xing[] = "Xing"; + const char info[] = "Info"; + char magic[4]; + if (deadbeef->fread (magic, 1, 4, buffer->file) != 4) { + trace ("cmp3_scan_stream: EOF while checking for Xing header\n"); return -1; // EOF } - flags = extract_i32 (buf); - if (flags & FRAMES_FLAG) { - // read number of frames + + // trace ("xing magic: %c%c%c%c\n", magic[0], magic[1], magic[2], magic[3]); + + if (!strncmp (xing, magic, 4) || !strncmp (info, magic, 4)) { + trace ("xing/info frame found\n"); + buffer->startoffset += packetlength; + // read flags + uint32_t flags; + uint8_t buf[4]; if (deadbeef->fread (buf, 1, 4, buffer->file) != 4) { trace ("cmp3_scan_stream: EOF while parsing Xing header\n"); return -1; // EOF } - uint32_t nframes = extract_i32 (buf); - buffer->duration = (float)nframes * (float)samples_per_frame / (float)samplerate; - trace ("xing totalsamples: %d, nframes: %d, samples_per_frame: %d\n", nframes*samples_per_frame, nframes, samples_per_frame); - if (nframes <= 0 || samples_per_frame <= 0) { - trace ("bad xing header\n"); - continue; + flags = extract_i32 (buf); + if (flags & FRAMES_FLAG) { + // read number of frames + if (deadbeef->fread (buf, 1, 4, buffer->file) != 4) { + trace ("cmp3_scan_stream: EOF while parsing Xing header\n"); + return -1; // EOF + } + uint32_t nframes = extract_i32 (buf); + buffer->duration = (float)nframes * (float)samples_per_frame / (float)samplerate; + trace ("xing totalsamples: %d, nframes: %d, samples_per_frame: %d\n", nframes*samples_per_frame, nframes, samples_per_frame); + if (nframes <= 0 || samples_per_frame <= 0) { + trace ("bad xing header\n"); + continue; + } + buffer->totalsamples = nframes * samples_per_frame; + buffer->samplerate = samplerate; + } + if (flags & BYTES_FLAG) { + deadbeef->fseek (buffer->file, 4, SEEK_CUR); + } + if (flags & TOC_FLAG) { + deadbeef->fseek (buffer->file, 100, SEEK_CUR); + } + if (flags & VBR_SCALE_FLAG) { + deadbeef->fseek (buffer->file, 4, SEEK_CUR); + } + // lame header + if (deadbeef->fread (buf, 1, 4, buffer->file) != 4) { + trace ("cmp3_scan_stream: EOF while reading LAME header\n"); + return -1; // EOF + } + // trace ("tell=%x, %c%c%c%c\n", deadbeef->ftell(buffer->file), buf[0], buf[1], buf[2], buf[3]); + if (!memcmp (buf, "LAME", 4)) { + trace ("lame header found\n"); + deadbeef->fseek (buffer->file, 6, SEEK_CUR); + + // FIXME: that can be optimized by single read + uint8_t lpf; + deadbeef->fread (&lpf, 1, 1, buffer->file); + //3 floats: replay gain + deadbeef->fread (buf, 1, 4, buffer->file); + // float rg_peaksignalamp = extract_f32 (buf); + deadbeef->fread (buf, 1, 2, buffer->file); + // uint16_t rg_radio = extract_i16 (buf); + + deadbeef->fread (buf, 1, 2, buffer->file); + // uint16_t rg_audiophile = extract_i16 (buf); + + // skip + deadbeef->fseek (buffer->file, 2, SEEK_CUR); + deadbeef->fread (buf, 1, 3, buffer->file); + uint32_t startdelay = (((uint32_t)buf[0]) << 4) | ((((uint32_t)buf[1]) & 0xf0)>>4); + uint32_t enddelay = ((((uint32_t)buf[1])&0x0f)<<8) | ((uint32_t)buf[2]); + // skip + deadbeef->fseek (buffer->file, 1, SEEK_CUR); + // mp3gain + uint8_t mp3gain; + deadbeef->fread (&mp3gain, 1, 1, buffer->file); + // skip + deadbeef->fseek (buffer->file, 2, SEEK_CUR); + // musiclen + deadbeef->fread (buf, 1, 4, buffer->file); + // uint32_t musiclen = extract_i32 (buf); + + //trace ("lpf: %d, peaksignalamp: %f, radiogain: %d, audiophile: %d, startdelay: %d, enddelay: %d, mp3gain: %d, musiclen: %d\n", lpf, rg_peaksignalamp, rg_radio, rg_audiophile, startdelay, enddelay, mp3gain, musiclen); + // skip crc + //deadbeef->fseek (buffer->file, 4, SEEK_CUR); + buffer->startdelay = startdelay; + buffer->enddelay = enddelay; + trace ("lame totalsamples: %d\n", buffer->totalsamples); + } + if (sample <= 0 && (flags&FRAMES_FLAG)) { + buffer->totalsamples -= buffer->enddelay; + deadbeef->fseek (buffer->file, framepos+packetlength-4, SEEK_SET); + return 0; } - buffer->totalsamples = nframes * samples_per_frame; - buffer->samplerate = samplerate; - } - if (flags & BYTES_FLAG) { - deadbeef->fseek (buffer->file, 4, SEEK_CUR); - } - if (flags & TOC_FLAG) { - deadbeef->fseek (buffer->file, 100, SEEK_CUR); - } - if (flags & VBR_SCALE_FLAG) { - deadbeef->fseek (buffer->file, 4, SEEK_CUR); - } - // lame header - if (deadbeef->fread (buf, 1, 4, buffer->file) != 4) { - trace ("cmp3_scan_stream: EOF while reading LAME header\n"); - return -1; // EOF - } -// trace ("tell=%x, %c%c%c%c\n", deadbeef->ftell(buffer->file), buf[0], buf[1], buf[2], buf[3]); - if (!memcmp (buf, "LAME", 4)) { - trace ("lame header found\n"); - deadbeef->fseek (buffer->file, 6, SEEK_CUR); - - // FIXME: that can be optimized by single read - uint8_t lpf; - deadbeef->fread (&lpf, 1, 1, buffer->file); - //3 floats: replay gain - deadbeef->fread (buf, 1, 4, buffer->file); - // float rg_peaksignalamp = extract_f32 (buf); - deadbeef->fread (buf, 1, 2, buffer->file); - // uint16_t rg_radio = extract_i16 (buf); - - deadbeef->fread (buf, 1, 2, buffer->file); - // uint16_t rg_audiophile = extract_i16 (buf); - - // skip - deadbeef->fseek (buffer->file, 2, SEEK_CUR); - deadbeef->fread (buf, 1, 3, buffer->file); - uint32_t startdelay = (((uint32_t)buf[0]) << 4) | ((((uint32_t)buf[1]) & 0xf0)>>4); - uint32_t enddelay = ((((uint32_t)buf[1])&0x0f)<<8) | ((uint32_t)buf[2]); - // skip - deadbeef->fseek (buffer->file, 1, SEEK_CUR); - // mp3gain - uint8_t mp3gain; - deadbeef->fread (&mp3gain, 1, 1, buffer->file); - // skip - deadbeef->fseek (buffer->file, 2, SEEK_CUR); - // musiclen - deadbeef->fread (buf, 1, 4, buffer->file); -// uint32_t musiclen = extract_i32 (buf); - - //trace ("lpf: %d, peaksignalamp: %f, radiogain: %d, audiophile: %d, startdelay: %d, enddelay: %d, mp3gain: %d, musiclen: %d\n", lpf, rg_peaksignalamp, rg_radio, rg_audiophile, startdelay, enddelay, mp3gain, musiclen); - // skip crc - //deadbeef->fseek (buffer->file, 4, SEEK_CUR); - buffer->startdelay = startdelay; - buffer->enddelay = enddelay; - trace ("lame totalsamples: %d\n", buffer->totalsamples); - } - if (sample <= 0 && (flags&FRAMES_FLAG)) { - buffer->totalsamples -= buffer->enddelay; - deadbeef->fseek (buffer->file, framepos+packetlength-4, SEEK_SET); - return 0; } } if (sample == 0) { diff --git a/plugins/oss/oss.c b/plugins/oss/oss.c index 6c1045bf..52c417fa 100644 --- a/plugins/oss/oss.c +++ b/plugins/oss/oss.c @@ -130,6 +130,7 @@ oss_init (void) { static int oss_change_rate (int rate) { if (!fd) { + oss_rate = rate; return oss_rate; } if (rate == oss_rate) { @@ -161,6 +162,7 @@ oss_free (void) { oss_terminate = 0; if (fd) { close (fd); + fd = 0; } if (mutex) { deadbeef->mutex_free (mutex); diff --git a/plugins/tta/ttaplug.c b/plugins/tta/ttaplug.c index f220d151..196df859 100644 --- a/plugins/tta/ttaplug.c +++ b/plugins/tta/ttaplug.c @@ -166,7 +166,7 @@ tta_read_int16 (DB_fileinfo_t *_info, char *bytes, int size) { if (size > 0 && !info->remaining) { info->remaining = get_samples (&info->tta, info->buffer); - if (!info->remaining) { + if (info->remaining <= 0) { break; } } diff --git a/plugins/vfs_curl/vfs_curl.c b/plugins/vfs_curl/vfs_curl.c index e73741a2..048f964e 100644 --- a/plugins/vfs_curl/vfs_curl.c +++ b/plugins/vfs_curl/vfs_curl.c @@ -391,6 +391,11 @@ http_content_header_handler (void *ptr, size_t size, size_t nmemb, void *stream) uint8_t key[256]; uint8_t value[256]; int refresh_playlist = 0; + + if (fp->length == 0) { + fp->length = -1; + } + while (p < end) { if (p <= end - 4) { if (!memcmp (p, "\r\n\r\n", 4)) { @@ -482,7 +487,7 @@ http_thread_func (void *ctx) { int status; - trace ("vfs_curl: started loading data\n"); + trace ("vfs_curl: started loading data %s\n", fp->url); for (;;) { struct curl_slist *headers = NULL; curl_easy_reset (curl); @@ -495,6 +500,7 @@ http_thread_func (void *ctx) { curl_easy_setopt (curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); curl_easy_setopt (curl, CURLOPT_HEADERFUNCTION, http_content_header_handler); curl_easy_setopt (curl, CURLOPT_HEADERDATA, ctx); + curl_easy_setopt (curl, CURLOPT_NOSIGNAL, 1); curl_easy_setopt (curl, CURLOPT_PROGRESSFUNCTION, http_curl_control); curl_easy_setopt (curl, CURLOPT_NOPROGRESS, 0); curl_easy_setopt (curl, CURLOPT_PROGRESSDATA, ctx); @@ -827,6 +833,7 @@ http_getlength (DB_FILE *stream) { assert (stream); HTTP_FILE *fp = (HTTP_FILE *)stream; if (fp->status == STATUS_ABORTED) { + trace ("length: -1\n"); return -1; } if (!fp->tid) { @@ -835,7 +842,7 @@ http_getlength (DB_FILE *stream) { while (fp->status == STATUS_INITIAL) { usleep (3000); } - //trace ("length: %d\n", fp->length); + trace ("length: %d\n", fp->length); return fp->length; } @@ -336,7 +336,7 @@ msgstr "Порядок" #: ../plugins/gtkui/interface.c:330 ../plugins/gtkui/deadbeef.glade.h:73 msgid "Linear" -msgstr "По порядку" +msgstr "По очереди" #: ../plugins/gtkui/interface.c:336 ../plugins/gtkui/deadbeef.glade.h:120 msgid "Shuffle" @@ -596,7 +596,7 @@ msgstr "Звук" #: ../plugins/gtkui/interface.c:1784 ../plugins/gtkui/deadbeef.glade.h:10 msgid "Allow dynamic samplerate switching" -msgstr "Разрешить динамическое преобразование частоты дискретизации" +msgstr "Разрешить динамическое изменение частоты дискретизации" #: ../plugins/gtkui/interface.c:1792 ../plugins/gtkui/deadbeef.glade.h:110 msgid "Samplerate conversion quality:" @@ -617,7 +617,7 @@ msgstr "Использовать пиковое значение" #: ../plugins/gtkui/interface.c:1832 ../plugins/gtkui/deadbeef.glade.h:5 msgid "Add files from command line (or file manager) to this playlist:" msgstr "" -"Добавлять файлы из командной строки (или файлового менеджера) в этот " +"Добавлять файлы из командной строки\n(или файлового менеджера) в этот " "плейлист:" #: ../plugins/gtkui/interface.c:1841 ../plugins/gtkui/deadbeef.glade.h:109 @@ -675,7 +675,7 @@ msgstr "Фон" #: ../plugins/gtkui/interface.c:1951 ../plugins/gtkui/deadbeef.glade.h:115 msgid "Seekbar/Volumebar colors" -msgstr "Цвета полосы проигрывания/регулятора громкости" +msgstr "Основные цвета" #: ../plugins/gtkui/interface.c:1969 ../plugins/gtkui/deadbeef.glade.h:79 msgid "Middle" @@ -524,6 +524,7 @@ streamer_song_removed_notify (playItem_t *it) { if (!mutex) { return; // streamer is not running } + streamer_lock (); if (it == playlist_track) { playlist_track = playlist_track->next[PL_MAIN]; // queue new next song for streaming @@ -532,6 +533,7 @@ streamer_song_removed_notify (playItem_t *it) { streamer_move_to_nextsong (0); } } + streamer_unlock (); } // that must be called after last sample from str_playing_song was done reading @@ -678,9 +680,6 @@ streamer_set_current (playItem_t *it) { } return -1; } -// if (bytes_until_next_song == -1) { -// bytes_until_next_song = 0; -// } success: plug_trigger_event_trackinfochanged (to); @@ -699,6 +698,10 @@ error: float streamer_get_playpos (void) { + float seek = seekpos; + if (seek >= 0) { + return seek; + } return playpos; } @@ -717,6 +720,7 @@ streamer_get_apx_bitrate (void) { void streamer_set_nextsong (int song, int pstate) { trace ("streamer_set_nextsong %d %d\n", song, pstate); + streamer_lock (); streamer_abort_files (); nextsong = song; nextsong_pstate = pstate; @@ -727,8 +731,9 @@ streamer_set_nextsong (int song, int pstate) { // no sense to wait until end of previous song, reset buffer bytes_until_next_song = 0; playpos = 0; -// seekpos = -1; + seekpos = -1; } + streamer_unlock (); } void @@ -847,13 +852,16 @@ streamer_thread (void *ctx) { gettimeofday (&tm1, NULL); if (nextsong >= 0) { // start streaming next song trace ("\033[0;34mnextsong=%d\033[37;0m\n", nextsong); + streamer_lock (); streamer_start_new_song (); + streamer_unlock (); // it's totally possible that song was switched // while streamer_set_current was running, // so we need to restart here continue; } else if (nextsong == -2 && (nextsong_pstate==0 || bytes_until_next_song == 0)) { + streamer_lock (); playItem_t *from = playing_track; bytes_until_next_song = -1; trace ("nextsong=-2\n"); @@ -875,6 +883,7 @@ streamer_thread (void *ctx) { if (from) { pl_item_unref (from); } + streamer_unlock (); continue; } else if (p_isstopped ()) { @@ -883,11 +892,13 @@ streamer_thread (void *ctx) { } if (bytes_until_next_song == 0) { + streamer_lock (); if (!streaming_track) { // means last song was deleted during final drain nextsong = -1; p_stop (); streamer_set_current (NULL); + streamer_unlock (); continue; } trace ("bytes_until_next_song=0, starting playback of new song\n"); @@ -952,16 +963,20 @@ streamer_thread (void *ctx) { if (p_play () < 0) { fprintf (stderr, "streamer: failed to start playback after samplerate change; output plugin doesn't work\n"); streamer_set_nextsong (-2, 0); + streamer_unlock (); continue; } } } + streamer_unlock (); } - if (seekpos >= 0) { - trace ("seeking to %f\n", seekpos); - float pos = seekpos; + int seek = seekpos; + if (seek >= 0) { + playpos = seek; seekpos = -1; + trace ("seeking to %f\n", seek); + float pos = seek; if (playing_track != streaming_track) { trace ("streamer already switched to next track\n"); diff --git a/translation/help.ru.txt b/translation/help.ru.txt index 5674fec0..ebc42d32 100644 --- a/translation/help.ru.txt +++ b/translation/help.ru.txt @@ -1,4 +1,4 @@ -Файл справки для плейера DeaDBeeF +Файл справки для плеера DeaDBeeF * ССЫЛКИ @@ -9,45 +9,43 @@ * ПОЛЕЗНЫЕ СОЧЕТАНИЯ КЛАВИШ - вы можете перемещаться по списку воспроизведения используя курсорные клавиши. + вы можете перемещаться по списку воспроизведения используя курсорные клавиши, а также клавиши PgUp, PgDn, Home и End. - клавиши PgUp, PgDn, Home, End также используются. + для выделения нескольких дорожек используются вышеперечисленные клавиши совметно с клавишей Shift. - для выделения нескольких композиций используются вышеперечисленные клавиши совметно с Shift. - - воспроизведение выполнено в стиле плейера Winamp™. + управление воспроизведением выполнено в стиле плеера Winamp™. Клавиши Z, X, C, V, B, N используются для следующих действий: - на предыдущую композицию, воспроизведение, пауза, остановка, следующая композиция, в случайном порядке. + переход на предыдущую дорожку, воспроизведение, пауза, остановка, переход на следующую дорожку, воспроизведение в случайном порядке. - в меню также представлены комбинации горячих клавиш. + дополнительные комбинации клавиш вы сможете найти в меню плеера. - пока все еще не доступна возможность переназначения горячих клавиш. + возможность переназначения клавиш всё ещё не доступна. * НАСТРОЙКА - начиная с версии 0.3.0 вся конфиграция хранится в отдельном файле: + начиная с версии 0.3.0 все настройки хранятся в отдельном файле: $HOME/.config/deadbeef/config - перед редактированием файла необходимо завершить работу с плейером, иначе настройки будут перезаписаны. + перед редактированием файла конфигурации необходимо завершить работу с плеером, иначе настройки будут перезаписаны. - большинство настроек могут быть изменены через диалоговое окно (Edit->Preferences dialog (Правка->Параметры)). + большинство настроек могут быть изменены через диалоговое окно (Правка -> Параметры). * ОБЛОЖКИ АЛЬБОМОВ - для отобржения обложек альбомов необходимо выполнить следующие шаги + для отображения обложек альбомов необходимо выполнить следующие шаги: - 1. добавьте новый столбец, выберите тип Album Art (Обложка альбома) - 2. клик правой кнопкой мыши на столбце списка воспроизведения, и выбор пункта "группировать по" в контекстном меню. Выбор "Исполнитель/Дата/Альбом". + 1. Добавить новый столбец, затем выбрать тип "Обложка альбома" + 2. Нажать правой кнопкой мыши на столбце списка воспроизведения, и выбрать пункт контекстного меню "Группировать по" -> "Исполнитель/Дата/Альбом". * ЭКРАННЫЕ УВЕДОМЛЕНИЯ - вы можете включить и настроить уведомления в настройках расширения "OSD Notify" (Экранные Уведомления). + вы можете включить и настроить уведомления в настройках расширения "OSD Notify" (Экранные уведомления). * МЕТАДАННЫЕ - для загрузки образов изображение+метаданные вам необходимо открыть/добавить существующие файлы изображений, и Deadbeef найдет корректный файл метаданных (.cue) автоматически. + для загрузки рипов образ+cue необходимо открыть/добавить существующие файлы образа, и DeadBeef автоматически найдет корректный файл метаданных (.cue). - вам не следует самостоятельно добавлять файлы метаданных (.cue) в список воспроизведения + вы не должны самостоятельно добавлять файлы метаданных (.cue) в список воспроизведения. пример: diff --git a/translators.txt b/translators.txt index 78977bc1..c8745820 100644 --- a/translators.txt +++ b/translators.txt @@ -81,6 +81,6 @@ Ukrainian Vietnamese Tran Duy Hung <nguyentieuhau@gmail.com> -Chinese (Taiwan, Province of China) +Traditional Chinese (Taiwan) Hong Jen Yee <pcman.tw@gmail.com> |