Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

they work as icecast servers and no longer depend on icecaast

  • Loading branch information...
commit 487f7c18aeb4d980b57afc7506b9fed7c8909b2a 1 parent d602c34
@drumsoft authored
View
3  .gitignore
@@ -0,0 +1,3 @@
+*~
+obsolete
+my-*
View
18 README
@@ -1,10 +1,18 @@
-CamTwist "icecast Song" plugin
+Traktor::SongNameServer, tweet-from-traktor.pl and CamTwist "Traktor Song" plugin
-"icecast Song" plugin (with a little hack to icecast2) displays track names playing on Traktor PCDJ to CamTwist virtual camera.
+日本語の説明は readme-j.txt を読んで下さい。
-other PCDJ products that supports icecast streaming will work with this plugin.
-Additionally, TweetFromTraktor.pl post the song names to twitter.
+[Traktor::SongNameServer]
+Traktor::SongNameServer is a server, receives song names from Traktor and provides them for other applications.
+It will work with other PCDJ softwares if they send icecast streaming protocols.
+To launch server, use songnameserver.pl .
-日本語の説明は readme-j.txt を読んで下さい。
+[tweet-from-traktor.pl]
+a script to post song names played by Traktor to twitter.
+This script works as server using Traktor::SongNameServer.
+
+[CamTwist "Traktor Song" plugin]
+a CamTwist plugin to display song names played by Traktor.
+This plugin fetches song names from Traktor::SongNameServer. then you should run it by songnameserver.pl or tweet-from-traktor.pl.
View
0  icecast Song.qtz → Traktor Song.qtz
File renamed without changes
View
257 Traktor/SongNameServer.pm
@@ -0,0 +1,257 @@
+use strict;
+use warnings;
+use utf8;
+
+package Traktor::SongNameServer;
+
+use IO::Socket;
+use IO::Select;
+
+sub new {
+ my $class = shift;
+ my %options = @_;
+ return bless {
+ host => $options{host} || 'localhost',
+ port => $options{port} || 8000,
+ buffer_size => $options{buffer_size} || 8192,
+ timeout => $options{timeout} || 1,
+ cueoptions => $options{cue} || {},
+ callback => $options{callback} || sub{},
+ song_artist => '',
+ song_title => '',
+ }, $class;
+}
+
+sub run {
+ my $self = shift;
+
+ my $cue = Traktor::SongNameServer::SongCue->new(
+ %{$self->{cueoptions}},
+ callback => sub {
+ my $song = shift;
+ $self->{song_artist} = $song->{ARTIST};
+ $self->{song_title} = $song->{TITLE};
+ $self->{callback}->($song);
+ }
+ );
+
+ my $sock_listen = new IO::Socket::INET(Listen=>5,
+ LocalAddr => $self->{host},
+ LocalPort => $self->{port},
+ Proto => 'tcp',
+ Reuse => 1);
+
+ die "IO::Socket : $!" unless $sock_listen;
+
+ my %workers;
+
+ my $selector = new IO::Select( $sock_listen );
+
+ while(1) {
+ my (@ready) = $selector->can_read($self->{timeout});
+ foreach my $sock (@ready) {
+ if($sock == $sock_listen) {
+ my $newsock = $sock_listen->accept;
+ $selector->add($newsock);
+ $workers{$newsock} = Traktor::SongNameServer::Worker->new($newsock, $self, $cue, $self->{buffer_size});
+ } else {
+ if ( ! $workers{$sock}->read() ) {
+ $workers{$sock}->finalize();
+ delete $workers{$sock};
+ $selector->remove($sock);
+ $sock->close();
+ }
+ }
+ }
+ $cue->noop();
+ }
+
+ close($sock_listen);
+}
+
+
+package Traktor::SongNameServer::Worker;
+use IO::Socket;
+use Encode qw/encode decode/;
+
+sub new {
+ my $class = shift;
+ my $sock = shift;
+ my $server = shift;
+ my $cue = shift;
+ my $buffer_size = shift;
+ my ($cl_port,$cl_iaddr) = unpack_sockaddr_in($sock->peername());
+
+ my $self = bless {
+ sock => $sock,
+ server => $server,
+ cue => $cue ,
+ buffer_size => $buffer_size,
+ client => inet_ntoa($cl_iaddr) . ':' . $cl_port,
+ input => '',
+ status => 'start', # 'start', 'header', 'data', 'meta', 'end'
+ mode => 'get' , # 'source', 'get'
+ }, $class;
+ return $self;
+}
+
+sub read {
+ my $self = shift;
+ my $buf;
+ my $length = sysread($self->{sock}, $buf, $self->{buffer_size});
+ if ( $length > 0 ) {
+ $self->{input} .= $buf;
+ }
+ my $keepconnect = $self->process();
+
+ return $length && $keepconnect;
+}
+
+sub finalize {
+ my $self = shift;
+ $self->process();
+ if ( $self->{mode} eq 'source' ) {
+ print "[source disconnected from $self->{client}]\n";
+ }
+}
+
+sub escape {
+ my $s = shift;
+ $s =~ s/&/&/;
+ $s =~ s/"/"/; #"
+ $s =~ s/</&lt;/;
+ $s =~ s/>/&gt;/;
+ $s;
+}
+
+sub response_songname {
+ my $self = shift;
+ my $out = $self->{sock};
+ print $out "HTTP/1.0 200 OK\r\n\r\n";
+ print $out qq{<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\r\n};
+ printf $out qq{<status><source><artist>%s</artist><title>%s</title></source></status>\r\n},
+ encode('utf8',escape($self->{server}->{song_artist})),
+ encode('utf8',escape($self->{server}->{song_title}));
+}
+
+sub response_ok {
+ my $self = shift;
+ my $out = $self->{sock};
+ print $out "HTTP/1.0 200 OK\r\n\r\n";
+}
+
+sub process {
+ my $self = shift;
+ while( 1 ) {
+ if ( $self->{status} eq 'start' || $self->{status} eq 'header' ) {
+ if ($self->{input} =~ s/^(.*?)\r\n//) {
+ # print $1, "\n";
+ if ( $self->{status} eq 'start' ) {
+ $self->{mode} = ($1 =~ /^SOURCE /i) ? 'source' : 'get';
+ $self->{status} = 'header';
+ if ( $self->{mode} eq 'source' ) {
+ print "[source connected from $self->{client}]\n";
+ }
+ } elsif ( ! defined $1 || $1 eq '' ) { # end of header
+ if ( $self->{mode} eq 'source' ) {
+ $self->response_ok();
+ $self->{status} = 'data';
+ } else {
+ $self->response_songname();
+ $self->{status} = 'end';
+ return 0;
+ }
+ }
+ } else {
+ last;
+ }
+ } elsif ( $self->{status} eq 'data' ) {
+ if ($self->{input} =~ s/^(?:.*?)(....)(ARTIST|TITLE)=//s) {
+ $self->{status} = 'meta';
+ $self->{metaname} = decode('utf8', $2);
+ $self->{metalength} = unpack('V',$1) - length($self->{metaname}) - 1;
+ } else {
+ if ( length($self->{input}) > 11 ) {
+ $self->{input} = substr($self->{input}, -11);
+ $self->{cue}->finish();
+ }
+ last;
+ }
+ } elsif ( $self->{status} eq 'meta' ) {
+ if ( length($self->{input}) >= $self->{metalength} ) {
+ my $metatext = decode('utf8', substr($self->{input}, 0, $self->{metalength}));
+ $self->{input} = substr($self->{input}, $self->{metalength});
+ $self->{status} = 'data';
+ $self->{cue}->add($self->{metaname}, $metatext);
+ } else {
+ last;
+ }
+ } else { # end
+ return 0;
+ }
+ }
+ return 1;
+ # (4byte/文字数 リトルエンディアン)(文字数byte/"(ARTIST|TITLE)=文字列")
+}
+
+
+package Traktor::SongNameServer::SongCue;
+
+sub new {
+ my $class = shift;
+ my %options = @_;
+ my $self = bless {
+ bysong => $options{bysong} || 0,
+ bytime => $options{bytime} || 0,
+ nextsongtimeout => $options{nextsongtimeout} || 0,
+ ignoreshortplay => $options{ignoreshortplay} || 0,
+ callback => $options{callback} || undef,
+ cue => [],
+ new => {},
+ }, $class;
+ return $self;
+}
+
+sub add {
+ my ($self, $name, $text) = @_;
+ $self->{new}->{$name} = $text;
+}
+
+sub finish {
+ my $self = shift;
+ if (keys %{$self->{new}}) {
+ my $now = time();
+ if ( $self->{ignoreshortplay} && @{$self->{cue}} &&
+ $self->{ignoreshortplay} > $now - $self->{cue}->[-1]->{time} ) {
+ pop @{$self->{cue}};
+ }
+ $self->{new}->{time} = $now;
+ push @{$self->{cue}}, $self->{new};
+ $self->{new} = {};
+ }
+}
+
+sub noop {
+ my $self = shift;
+ my $now = time();
+ if ( @{$self->{cue}} ) {
+ if ( $self->{bysong} < @{$self->{cue}} ) {
+ if (!$self->{bytime} ||
+ $self->{bytime} < $now - $self->{cue}->[$self->{bysong}]->{time} ) {
+ $self->fire();
+ }
+ }elsif ($self->{nextsongtimeout} &&
+ $self->{nextsongtimeout} < $now - $self->{cue}->[0]->{time} ) {
+ $self->fire();
+ }
+ }
+}
+
+sub fire {
+ my $self = shift;
+ $self->{callback}->(shift @{$self->{cue}});
+}
+
+1;
+
+
View
85 TweetFromTraktor.pl
@@ -1,85 +0,0 @@
-#!/usr/bin/perl
-
-use LWP::Simple;
-use Net::Twitter;
-use Data::Dumper;
-
-my $delay = 1; # tweet will be delayed for N song loadings.
-my $pollinginterval = 15; # in seconds
-my $postfix = " #HashTagOfMyTraktorPlay"; # postfix of tweets
-my $url = "http://localhost:8000/"; # stream url
-
-# create your new application and
-# get information from https://dev.twitter.com/apps
-my $nt = Net::Twitter->new(
- traits => [qw/OAuth API::REST/],
- consumer_key => '',
- consumer_secret => '',
- access_token => '',
- access_token_secret => '',
-);
-
-sub main {
- my $prev = "";
- my @cur = ();
- while(1) {
- my $tr = getTrackName();
- if ( defined $tr ) {
- if ($prev ne $tr) {
- unshift @cue, $tr;
- $prev = $tr;
- }
- if (@cue > $delay) {
- $tr = pop @cue;
- tweet($tr);
- }
- }
- sleep($pollinginterval);
- }
-}
-
-sub getTrackName {
- my $src = get($url);
- my $artist = $src =~ /<artist>([^<>]+)<\/artist>/ ?
- $1 : undef;
- my $title = $src =~ /<title>([^<>]+)<\/title>/ ?
- $1 : undef;
- return $artist && $title ?
- qq{$artist / "$title"} : undef;
-}
-
-sub tweet {
- my $tw = (shift) . $postfix;
- my $result = $nt->update($tw);
- print $tw . "\n";
-}
-
-main();
-
-__END__
-
-TweetFromTraktor.pl
-
-[概要]
-twitterに、Traktorでプレイ中の曲名をポストするスクリプトです。
-
-TweetFromTraktor.pl は起動しっぱなしになり、定期的に Traktor からのストリームの曲名をチェックし、変更があったら Twitter にポストします。
-
-曲名の変更はデフォルトで1曲分遅れるので、デッキにセットしてチェック・調整中の曲名が流される事はありません。その曲のプレイが始まって、次にかける曲をチェック・調整し始めたあたりで Tweet が行われます。
-
-
-[使い方]
-1. https://dev.twitter.com/apps にアクセスして、Twitterへポストするための新しいアプリケーションを登録する
-
-2. 同サイトから下記を取得して設定に記入
- Consumer key
- Consumer secret
- Access token
- Access token secret
-
-3. $postfix 等を調整する(イベントのハッシュタグにするとか)
-
-4. CamTwist "icecast Song" plugin のマニュアル記載の方法で修正した icecast を起動し Traktor からストリームを行う( readme-j.txt 記載の C-1, C-2 の手順)
-
-5. TweetFromTraktor.pl を起動する(起動しっぱなしになる)
-
View
161 readme-j.txt
@@ -1,106 +1,125 @@
-CamTwist "icecast Song" plugin
-
-Traktor でプレイ中の曲名を CamTwist に表示させる事ができます。
+Traktor::SongNameServer, tweet-from-traktor.pl and CamTwist "Traktor Song" plugin
+[Traktor::SongNameServer]
+Traktor でプレイ中の曲名を受信し、他のソフトから利用できる様にするサーバです。
他の PCDJ ソフトでも、icecastストリーミングプロトコルに対応しているものであれば動作する筈です。
+起動するには songnameserver.pl を使ってください。
-TweetFromTraktor.pl を追加しました。これはプレイ中の曲名をさらに twitter にポストする為のスクリプトです。
-
-
-[概要]
-
-"icecast Song" は TRAKTOR(等のPCDJ)で再生中の曲名情報を icecast2 サーバから取得します。
-TRAKTORはプレイ中のサウンドを曲名情報と一緒に icecast2 サーバにストリーミング送信します。
+[tweet-from-traktor.pl]
+Traktor::SongNameServer を使って、Traktor でプレイ中の曲名を Twitter にポストします。
-      ┌───────┐
-      │icecast2サーバ│
-      └───────┘
-UPSTREAM DJing↑     ↓FETCH song info
-    ┌──┴─┐ ┌─┴────────┐
-    │TRAKTOR │ │icecast Song plugin │
-    └────┘ └──────────┘
+[CamTwist "Traktor Song" plugin]
+Traktor::SongNameServer と通信して、Traktor でプレイ中の曲名を CamTwist に表示します。
-icecast2 は MacPorts からインストールすると簡単です。
-MacPortsの使い方は検索して下さい。
+[概要]
-[マニュアル]
-下記 [Adv.] がついている項目は、 icecast2 を Macports を使わずにインストールしたり、サーバのポート番号を変更したりといった事をする「よくわかってる人」向けの説明です。
+Traktor::SongNameServer を起動すると icecast サーバとして動作します。
+Traktor でこのサーバを Broadcasting Server に設定する事で、プレイした曲名データが Traktor::SongNameServer に送信されます。
-A.インストール
+CamTwist "Traktor Song" plugin は Traktor::SongNameServer から曲名情報を取得します。
-A-1. icecast2 をインストールする
- MacPorts でインストールする場合
- sudo port install icecast2
+tweet-from-traktor.pl は Traktor::SongNameServer からコールバックされ、Twitterに曲名情報を含むツイートをポストします。
- [Adv.]ソースからインストールした場合等は、以下の /opt/local/.. を適宜 /usr/local/.. 等に読み替える事
+      ┌────────────┐Callback┌───────────┐
+      │Traktor::SongNameServer │------→│tweet-from-traktor.pl │
+      └────────────┘    └───────────┘
+UPSTREAM DJing↑     ↓FETCH song info
+    ┌──┴─┐ ┌─┴───────┐
+    │Traktor │ │Traktor Song plugin│
+    └────┘ └─────────┘
-A-2. icecast2 の status.xsl を同梱の物に置き換える
- status.xsl は以下にインストールされているので、これを置き換える
- /opt/local/share/icecast/web/
-A-3. icecast2 の設定を行う
- /opt/local/etc/icecast.xml を変更する
- authenticationブロックのパスワードを設定する(必須)
+ cue.bysong: 曲名の更新を、n曲分遅らせる
+ cue.bytime: 曲名の更新を、n秒遅らせる
+ cue.nextsongtimeout: 次の曲がn秒プレイされなかったら、bysongの設定を無視して曲名を更新する
+ cue.ignoreshortplay: n秒未満しか再生されなかった曲は、プレイされなかった事にする
- [Adv.]もしも必要なら以下も変更する
- hostname
- listen-socket ブロックの port (ポート番号)
- ここを変更した場合は以下の "localhost" "8000" を変更した物に読み替える事
-A-4. icecast2 のログ出力先を作る
- icecast2のログファイルの出力先
- /opt/local/var/log/icecast/
- が必要なのでこれを作る
- mkdir -p /opt/local/var/log/icecast
- 自分が書き込めるパーミッションを設定する。
+[マニュアル]
+下記 [Adv.] がついている項目は、「よくわかってる人」向けの説明です。
-A-5. icecast Song.qtz をインストールする
- 下記のどちらかに icecast Song.qtz ファイルを置く
- CamTwist インストールフォルダの Effects フォルダ
- ~/Library/Application Support/CamTwist/Effects
+[A: Traktor::SongNameServer を使える様にしよう]
-B.icecast2 のテスト
+A-1. インストールと設定
+ このフォルダを任意の場所に置く
+ [Adv.] songnameserver.pl を開いて、設定を変更する
+ host, port: サーバの待ち受けアドレスとポート番号
+ ※ここを変更した場合は以下の "localhost" "8000" を変更した物に読み替える
+ (WindowsやLinuxの人は、Perlをインストールしておく)
-B-1. Traktor のストリーミング設定を行う
+A-2. Traktor のストリーミング設定を行う
Preference > Broadcasting > Server Settings
Address: localhost Port: 8000
- Password: A-3で設定したもの
- Format: 音は聴かないので何でも良い
+ Password: 空白
+ Format: Ogg Vorbis, 11025 Hz, 32kBit/s
-B-2. icecast2 を起動する
- icecast -c /opt/local/etc/icecast.xml
+A-3. Traktor::SongNameServer を起動
+ ターミナルで perl songnameserver.pl を実行
-B-3. Traktor でストリーミングを開始する
+A-4. Traktor でストリーミングを開始する
AUDIO RECORDER ペインを開き、STREAMINGボタンを押す
ストリーミングに成功するとボタンが光る
+ (失敗すると、ボタンは点滅する)
+
+A-5. 曲名を更新させて、表示を確認する
+ 曲を 2, 3 曲再生すると、曲名が更新される。
+ 更新された曲名は songnameserver.pl を実行中のターミナルに表示される。
+ また、ブラウザで http://localhost:8000/ にアクセスすると曲名が表示される。
+
+
+[B: CamTwist "Traktor Song" plugin を使おう]
+
+B-1. Traktor Song.qtz をインストール
+ 下記のどちらかに Traktor Song.qtz ファイルを置く
+ ・ CamTwist インストールフォルダの Effects フォルダ
+ ・ ~/Library/Application Support/CamTwist/Effects
+
+B-2. Traktor からストリーミングを開始する
+ A-3, A-4 を行ってください。
+
+B-3. CamTwist で "Traktor Song" エフェクトをADDして、好みの表示設定にする
+ [Adv.] ホストやポート名の設定を変えた場合は "icecast URL" を変更する
+ 文字サイズや表示位置などを調整する
-B-4. ブラウザでストリーミングの曲名を確認する
- ブラウザで下記のアドレスを開く
- http://localhost:8000/
- 下記の様な曲名を載せたXMLファイルが見えたら成功
- <status>
- <source>
- <mountpoint>/</mountpoint>
- <artist>takuya</artist>
- <title>364 Nights</title>
- </source>
- </status>
+曲名が更新されると CamTwist の画面に反映されます。
+二回目以降は B-2 から行ってください。
- なお、曲名が更新されるタイミングは「新しく曲をデッキにロードして、デッキを再生してしばらく後」なので、テストの際はストリーミング開始後に必ず曲をデッキにロードしてから再生する必要がある
+[C: tweet-from-traktor.pl を使おう]
-C. "icecast Song" プラグインの使用手順
+C-1. Net::Twitter モジュールのインストール
+ cpan や cpanm を使い Net::Twitter をインストールする
-C-1. icecast2 を起動する(参照:B-2)
+C-2. Twitter アプリケーションを登録
+ https://dev.twitter.com/apps にアクセスして、Twitterへポスト
+ するための新しいアプリケーションを登録する
+ 登録したアプリケーションのページから、以下を取得する
+ Consumer key
+ Consumer secret
+ Access token
+ Access token secret
-C-2. Traktor からストリーミングを開始する(参照:B-3)
+C-2. tweet-from-traktor.pl を設定
+ tweet-from-traktor.pl を開き、取得した4つの文字列を
+ consumer_key => '',
+ consumer_secret => '',
+ access_token => '',
+ access_token_secret => '',
+ に設定する。
+ また A-1 で行った設定があれば、 songnameserver.pl にも同じ設定を行う。
+ 【重要】 $postfix をいい感じにする(イベントのハッシュタグとか)
-C-3. CamTwist で "icecast Song" エフェクトをADDする
+C-3. tweet-from-traktor.pl を起動
+ ターミナルで perl tweet-from-traktor.pl を実行
+ (tweet-from-traktor.pl が Traktor::SongNameServer を起動するので、
+ songnameserver.pl は使いません)
-C-4. [Adv.] A-3でホストやポート名の設定を変えた場合は "icecast URL" を変更する
+C-4. Traktor からストリーミングを開始する
+ A-4 を行ってください。
-C-5. 必要なら "icecast Song" の文字サイズや表示位置などを調整する
+曲名が更新されると、Twitterにポストされます。
+二回目以降は C-3 から行ってください。
View
39 songnameserver.pl
@@ -0,0 +1,39 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Traktor::SongNameServer;
+use Encode qw/encode/;
+
+my %option = (
+ host => 'localhost', # server address
+ port => 8000, # server port
+
+ cue => {
+ # Song Name Cue Setting - song names are cued and displayed after delay.
+ # delay <bysong> songs to display song names.
+ bysong => 1,
+ # delay <bytime> seconds to display song names.
+ bytime => 0,
+ # when both <bysong> and <bytime> are not zero, they are summed.
+ # when <bysong> is not zero and next song are not coming,
+ # cued songname will be displayed after <nextsongtimeout> seconds.
+ # 0 means no timeout.
+ nextsongtimeout => 180,
+ # when song played shorter than <ignoreshortplay> seconds, ignore it.
+ # 0 means nothing ignored.
+ ignoreshortplay => 5,
+ },
+
+ buffer_size => 8192, # stream receive buffer size
+ timeout => 1, # stream receive loop timeout in second
+ callback => sub {
+ my $song = shift;
+ print encode('utf8', qq{[song changed: $song->{ARTIST} / "$song->{TITLE}"]\n});
+ }, # callback for song name changed.
+ # an argument is {ARTIST=>'...', TITLE=>'...'}.
+);
+
+my $server = Traktor::SongNameServer->new(%option);
+$server->run();
+
View
20 status.xsl
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-<xsl:stylesheet xmlns:xsl = "http://www.w3.org/1999/XSL/Transform" version = "1.0" >
-<xsl:template match = "/icestats" >
-
-<status>
-<xsl:for-each select="source">
- <source>
- <mountpoint><xsl:value-of select="@mount" /></mountpoint>
- <xsl:if test="artist">
- <artist><xsl:value-of select="artist" /></artist>
- </xsl:if>
- <xsl:if test="title">
- <title><xsl:value-of select="title" /></title>
- </xsl:if>
- </source>
-</xsl:for-each>
-</status>
-
-</xsl:template>
-</xsl:stylesheet>
View
55 tweet-from-traktor.pl
@@ -0,0 +1,55 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use utf8;
+use Net::Twitter;
+use Traktor::SongNameServer;
+use Encode qw/encode/;
+
+my $postfix = " #HashTagOfMyTraktorPlay"; # postfix of tweets
+
+# create your new application and
+# get information from https://dev.twitter.com/apps
+my $nt = Net::Twitter->new(
+ traits => [qw/OAuth API::REST/],
+ consumer_key => '',
+ consumer_secret => '',
+ access_token => '',
+ access_token_secret => '',
+);
+
+my %option = (
+ host => 'localhost', # server address
+ port => 8000, # server port
+
+ cue => {
+ # Song Name Cue Setting - song names are cued and displayed after delay.
+ # delay <bysong> songs to display song names.
+ bysong => 1,
+ # delay <bytime> seconds to display song names.
+ bytime => 0,
+ # when both <bysong> and <bytime> are not zero, they are summed.
+ # when <bysong> is not zero and next song are not coming,
+ # cued songname will be displayed after <nextsongtimeout> seconds.
+ # 0 means no timeout.
+ nextsongtimeout => 180,
+ # when song played shorter than <ignoreshortplay> seconds, ignore it.
+ # 0 means nothing ignored.
+ ignoreshortplay => 5,
+ },
+
+ buffer_size => 8192, # stream receive buffer size
+ timeout => 1, # stream receive loop timeout in second
+ callback => sub {
+ my $song = shift;
+ my $result = $nt->update(sprintf '%s / "%s" %s',
+ $song->{ARTIST}, $song->{TITLE}, $postfix);
+ print encode('utf8', qq{[song tweeted: $song->{ARTIST} / "$song->{TITLE}"]\n});
+ }, # callback for song name changed.
+ # an argument is {ARTIST=>'...', TITLE=>'...'}.
+);
+
+my $server = Traktor::SongNameServer->new(%option);
+$server->run();
+
Please sign in to comment.
Something went wrong with that request. Please try again.