블로그 이미지
* Microsoft MVP - Windows Development 2014 ~ 2019 5ring * LINE : kaki104 * facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/ kaki104

카테고리

List All (551)
Xamarin Forms (4)
Bot Framework (19)
Azure (9)
Windows 10 (35)
Facebook News & Tips (158)
Windows App(Universa.. (83)
Windows 8&8.1 (113)
Windows Phone 8 (42)
Silverlight (37)
HTML5 & MVC4 (16)
WPF (1)
Portable Class Library (2)
Uncategorised Tips a.. (3)
Kinect for Windows (2)
ETC (12)
kaki104 Scrap (4)
App News (11)
Total521,318
Today37
Yesterday108


Amazon Polly를 이용해서 TTS를 만드는 간단한 셈플을 만들어 보도록 하겠습니다.


완성이되면 이런 화면에서 한글을 입력하고 폴리야~ 버튼을 클릭하면 설현(?) 목소리로 내용을 읽어 줍니다.



1. 준비


1) AWS를 이용하기 때문에 Amazon Web Service 계정이 필요합니다.

저도 없어서 새로 만들었습니다.


https://aws.amazon.com/ko/polly/


이 페이지를 상단에 무료 계정 생성하기 버튼을 클릭해서 계정을 만들면 됩니다.


2) 계정을 만들고 나면, IAM 관리자 및 계정을 만들어 주어야 합니다.


https://docs.aws.amazon.com/ko_kr/IAM/latest/UserGuide/getting-started_create-admin-group.html


이 페이지를 참고하셔서 IAM 계정을 생성하고, 그룹도 추가해 주시면 됩니다.


3) Polly 서비스를 사용하기 위해서 AccessKeyID와 SecretAccessKey가 필요합니다.


https://docs.aws.amazon.com/ko_kr/IAM/latest/UserGuide/id_credentials_access-keys.html


이 페이지를 참고하시면 만드실 수 있습니다.



2. 프로젝트 생성


Visual Stduio 2017를 실행하고, Windows Template Studio를 이용해서 단일 화면 프로젝트를 생성 했습니다.




3. NuGet Package 설치


AWSSDK.Core, AWSSDK.Polly 2개의 패키지를 선택해서 설치 합니다.



4. MainPage.xaml


            <StackPanel Grid.Row="1" Grid.Column="1">
                <TextBox Header="한글을 입력하세요" Text="{Binding InputText, Mode=TwoWay}"/>
                <Button Content="폴리야" Command="{Binding TTSCommand}"/>
                <MediaElement x:Name="MediaElement" RealTimePlayback="True">
                    <interactivity:Interaction.Behaviors>
                        <core:EventTriggerBehavior EventName="MediaEnded">
                            <core:InvokeCommandAction Command="{Binding MediaEndedCommand}"/>
                        </core:EventTriggerBehavior>
                        <behaviors:MediaBehavior Stream="{Binding RandomAccessStream, Mode=TwoWay}"/>
                    </interactivity:Interaction.Behaviors>
                </MediaElement>
            </StackPanel>



일반적인 코드입니다. 여기서 사용된 MediaBehavior는 지난번에 공유했던 내용이라 설명은 생략 합니다.


내부 코드는 좀 변경이 되었으니 맨 아래 공유해드리는 소스를 참고 하시면 될 것 같습니다.



5. MainViewModel.cs


        /// <summary>
        ///     TTS 커맨드 실행
        /// </summary>
        private async void ExecuteTTSCommand()
        {
            if (string.IsNullOrEmpty(InputText)) return;

            //폴리 클라이언트 생성
            var pc = new AmazonPollyClient("AccessKeyID를 입력하세요", "SecretAccessKey를 입력하세요"
                , RegionEndpoint.APNortheast2);

            //요청 생성
            var sreq = new SynthesizeSpeechRequest
            {
                Text = $"<speak>{InputText}</speak>",
                OutputFormat = OutputFormat.Mp3,
                VoiceId = VoiceId.Seoyeon,
                LanguageCode = "ko-KR",
                TextType = TextType.Ssml
            };

            InputText = string.Empty;

            //서비스 요청
            var sres = await pc.SynthesizeSpeechAsync(sreq);

            //서비스 요청 결과 확인
            if (sres.HttpStatusCode != HttpStatusCode.OK)
                return;

            //파일명 생성
            var fileName = $@"{ApplicationData.Current.LocalFolder.Path}\{DateTime.Now:yyMMddhhmmss}.mp3";
            //파일에 AudioStream 쓰기
            using (var fileStream = File.Create(fileName))
            {
                sres.AudioStream.CopyTo(fileStream);
                fileStream.Flush();
                fileStream.Close();
            }
            //생성된 파일을 가져오기
            var file = await StorageFile.GetFileFromPathAsync(fileName);
            //파일을 열어서 RandomAccessStream 프로퍼티에 입력
            RandomAccessStream = await file.OpenAsync(FileAccessMode.Read);

            //RandomAccessStream과 바인딩이 되어있는 MediaBehavior에서 MediaPlayer를 통해서 재생
        }


리즌 종류는 여기서 확인하세용

https://docs.aws.amazon.com/ko_kr/general/latest/gr/rande.html



6. 다운 받은 셈플 보이스



7. 소스


https://github.com/kaki104/AWSSamples



Posted by MVP kaki104
TAG aws, Polly

페이스북 그룹에 올려주신 문의 내용에 맞는 셈플을 만들어 보았습니다.
https://www.facebook.com/groups/w10app 



Q. 안녕하세요.
숫자 키패드에 마우스 클릭이나 키보드 둘 다 입력이 가능하도록 mvvm을 사용하여 구현하고 싶습니다.
기존 event-driven에서는 두 개의 event를 구현해서 button에 연결하면 되었는데,이를 mvvm으로 하려니 문제가 생겼네요.
제가 알기로는 mvvm하에서는 xaml상에서 버튼 컨트롤에 command로 연결해줘야 하는 것으로 알고 있습니다. 즉 버튼 컨트롤에 하나의 이벤트만 커맨드로 처리할 수 있다고 저는 알고 있는데, 이러면 이벤트 두 개 중 하나만 구현 가능한 건가요?
mvvm하에서도 event-driven과 같이 마우스 클릭이나 키보드 둘 다 사용 가능하게 구현하고 싶은데, 이를 어떻게 구현해야 하나요?



A. 버튼에 단축키를 설정하면 간단하게 해결하실 수 있습니다.


참고

https://docs.microsoft.com/en-us/windows/uwp/design/input/keyboard-accelerators


                <!--숫자 키패드는 키를 추가해줘야 합니다.-->
                <Button Grid.Column="0" Grid.Row="0" Content="1"
                        Command="{Binding InputCommand}" CommandParameter="1">
                    <Button.KeyboardAccelerators>
                        <KeyboardAccelerator Key="Number1" />
                    </Button.KeyboardAccelerators>

                </Button>


1버튼에 숫자키1를 단축키로 설정하고, InputCommand를 실행 합니다. 실행할 때 CommandParameter로 1을 넘겨 줍니다.



        private void Init()
        {
            InputNumbers = "0";
            InputCommand = new RelayCommand<object>(ExecuteInputCommand);
        }

        /// <summary>
        ///     인풋 커맨드 실행
        /// </summary>
        /// <param name="obj"></param>
        private void ExecuteInputCommand(object obj)
        {
            if (!(obj is string number)) return;

            
            var num = int.Parse(InputNumbers.Replace(",", ""));

            //backspace키는 shell에서 사용하고 있기 때문에 사용이 않되는 듯..
            switch (number)
            {
                case "*":
                    break;
                case "B":
                    num = num.ToString().Length == 1
                        ? 0
                        : int.Parse(num.ToString().Substring(0, num.ToString().Length - 1));
                    break;
                default:
                    num = int.Parse(num + number);
                    break;
            }

            InputNumbers = string.Format("{0:N0}", num);
        }


InputNumbers라는 프로퍼티에 숫자를 string.Format을 이용해서 변환을 해서 집어 넣습니다.


그러면 3자리마다 컴마가 입력됩니다. 숫자 FormatString에 대한 자세한 사항은 검색을 이용하면 쉽게 찾으실 수 있습니다.


BackSpace키는 쉘에서 네비게이션 Back을 하는 키로 사용되고 있어서 B키를 하나 지우는 키로 사용했습니다. 


Windows Tempalte Studio를 이용해서 만든 앱이 아니라면, 사용이 가능할 것이라고 생각됩니다.


자세한 내용은 소스를 참고하세요


깃허브 소스

https://github.com/kaki104/BasicSamples


Posted by MVP kaki104

UWP앱들을 보면 검색 버튼을 클릭하면 택스트 박스가 출력되는 UI를 자주 볼 수 있습니다.


음..기본으로 제공되는 줄 알았는데..그러지는 않더군요..ㅋㅋ

간단하게 만들어보았습니다.


1. XAML


<Page>

    <Page.Resources>

        <Style x:Key="SearchBoxStyle" TargetType="FlyoutPresenter">
            <Setter Property="Margin" Value="78,0,0,0"/>
            <Setter Property="Padding" Value="5"/>
            <Setter Property="Height" Value="50"/>
        </Style>

    </Page.Resources>

    <Grid>


        <CommandBar Background="Transparent" IsOpen="False" DefaultLabelPosition="Right">
            <AppBarButton Icon="Find" Label="Find">
                <AppBarButton.Flyout>
                    <Flyout Placement="Left" FlyoutPresenterStyle="{StaticResource SearchBoxStyle}"
                            Opened="FlyoutBase_OnOpened">
                        <AutoSuggestBox x:Name="SuggestBox" PlaceholderText="Input words to search" AllowFocusOnInteraction="True" />
                    </Flyout>
                </AppBarButton.Flyout>
            </AppBarButton>

            <AppBarButton Icon="Add" Label="Add" Command="{Binding AddCommand}"/>
        </CommandBar>

    </Grid>

</Page>



2. XAML.CS


        private void FlyoutBase_OnOpened(object sender, object e)
        {
            SuggestBox.Focus(FocusState.Programmatic);
        }



3. 설명


여기서 핵심은 AppBarButton의 Flyout을 이용해서 툴팁 같은 팝업을 출력하는 것입니다. 그 내부에 메뉴도 넣을 수 있고, 머 여러가지로 활용을 하는데.. 이번에는 AutoSuggestBox를 집어 넣은 것입니다.


AutoSuggestBox가 Windows 10의 기본 검색 컨트롤이라고 하네요..음..SearchBox 컨트롤도 존재하지만, 이전 버전에서 사용되는 컨트롤이라...


검색 버튼을 누르기전




검색 버튼을 누른 후


검색 버튼을 누른 후 포커스를 넣기 위해서 CS에서 코딩을 하나 했습니다.

CS 코딩이 싫은 분들은 Behavior를 만들어서 사용하시면 됩니다.


Posted by MVP kaki104



NAS에 MariaDB(MySQL)를 설치는 했는데..어떻게 사용해야 할까요? (약 1시간 정도 고민..) 

역시 가장 만만한 RESTfull 서비스를 만들어서 호출하면 되겠죠? 그렇다면, Azure에서 RESTfull 서비스를 만들어서 CRUD를 해 볼까요?


MySQL과 연결하는 서비스를 만드는 것이 저도 처음이라 엄청난 삽질을 했습니다. ㅋㅋ 간단하지는 않네요.



0. 참고

NAS에 Database를?

NorthWind Database 생성

Visual Studio를 사용하여 Azure App Service에서 웹앱 문제 해결

ERROR 1044 (42000): Access denied for user ''@'localhost' to database 'db'

Connect C# to MySQL



1. MySQL에 NorthWind Database 생성


참고에 있는 NorthWind Database 생성 페이지에서 Northwind.MySQL5.sql을 다운로드 합니다.


SQLyog 프로그램을 실행해서 DB에 접속하고, 다운로드받은 쿼리를 불러온 후 실행하면, northwind db가 생성됩니다.




2. 셈플 프로젝트 생성


Visual Studio를 실행하고 New -> Project를 선택하고 다음과 같이 선택합니다.



ASP.NET Web Application을 선택합니다. 여기서 해당 내용들이 나오지 않는다면, 비주얼 스튜디오를 설치할 때 웹 관련 항목을 선택하지 않았기 때문입니다. 그리고, Azure와 관련된 최신 업데이트를 완료하세요.


OK를 입력하여 다음 화면으로 넘어 갑니다.



여기서 Azure API App을 선택하고, OK를 클릭합니다.


프로젝트 생성 완료!



3. MySQL 사용을 위한 준비


프로젝트에서 오른쪽 버튼을 눌러 NuGet packages를 선택합니다.


Browse 탭에서 MySQL을 검색해서 MySQL.Data를 선택하고 설치합니다.




4. MySQLHelper


DB에 접속해서 쿼리를 실행시키는 부분을 헬퍼 클래스로 분리 합니다.


    /// <summary>
    ///     MySQL 헬퍼 클래스
    /// </summary>
    public class MySQLHelper
    {
        private static MySQLHelper _instance;

        private MySqlConnection _connection;

        /// <summary>
        ///     생성자
        /// </summary>
        public MySQLHelper()
        {
            Initialize();
        }

        /// <summary>
        ///     인스턴스
        /// </summary>
        public static MySQLHelper Instance => _instance ?? (_instance = new MySQLHelper());

        /// <summary>
        ///     초기화
        /// </summary>
        private void Initialize()
        {
            var server = "NAS 외부접속 아이피";
            var database = "northwind";
            var uid = "kakiadmin";
            var password = "비밀번호";
            var connectionString =
                $"SERVER={server};DATABASE={database};UID={uid};Pwd={password};SslMode=none;";

            _connection = new MySqlConnection(connectionString);
        }

        /// <summary>
        ///     연결
        /// </summary>
        /// <returns></returns>
        private Tuple<bool, string> OpenConnection()
        {
            try
            {
                _connection.Open();
                return new Tuple<bool, string>(true, null);
            }
            catch (MySqlException ex)
            {
                //When handling errors, you can your application's response based
                //on the error number.
                //The two most common error numbers when connecting are as follows:
                //0: Cannot connect to server.
                //1045: Invalid user name and/or password.
                switch (ex.Number)
                {
                    case 0:
                        Debug.WriteLine("Cannot connect to server.  Contact administrator");
                        break;

                    case 1045:
                        Debug.WriteLine("Invalid username/password, please try again");
                        break;
                }

                return new Tuple<bool, string>(false, ex.Message);
            }
        }

        /// <summary>
        ///     연결 종료
        /// </summary>
        /// <returns></returns>
        private bool CloseConnection()
        {
            try
            {
                _connection.Close();
                return true;
            }
            catch (MySqlException ex)
            {
                Debug.WriteLine(ex.Message);
                return false;
            }
        }

        /// <summary>
        ///     연결 테스트
        /// </summary>
        /// <returns></returns>
        public async Task<string> GetConnectTestAsync()
        {
            var ds = new DataSet();
            var query = "SELECT * FROM Employees";
            var result = OpenConnection();
            if (result.Item1 == false) return result.Item2;
            var adpt = new MySqlDataAdapter(query, _connection);
            //var cmd = new MySqlCommand(query, _connection);
            await adpt.FillAsync(ds);
            CloseConnection();
            return "success connecting";
        }

        /// <summary>
        ///     결과 반환하지 않는 쿼리 실행, Insert, Update, Delete
        /// </summary>
        /// <param name="query"></param>
        /// <returns></returns>
        public async Task<string> ExecuteNonQueryAsync(string query)
        {
            if (string.IsNullOrEmpty(query)) return "Empty strings are not supported.";
            var result = OpenConnection();
            if (result.Item1 == false) return result.Item2;
            var cmd = new MySqlCommand(query, _connection);
            var resultExecute = await cmd.ExecuteNonQueryAsync();
            CloseConnection();
            return resultExecute.ToString();
        }

        /// <summary>
        ///     결과 반환하는 쿼리 실행 Select
        /// </summary>
        /// <param name="query"></param>
        /// <returns></returns>
        public async Task<DataSet> ExecuteAsync(string query)
        {
            if (string.IsNullOrEmpty(query)) return null;
            var ds = new DataSet();
            var result = OpenConnection();
            if (result.Item1 == false) return null;
            var adpt = new MySqlDataAdapter(query, _connection);
            await adpt.FillAsync(ds);
            CloseConnection();
            return ds;
        }
    }


Initialize()에 접속 아이디와 비밀번호는 지난번 포스트에서 이야기 했듯이 telnet으로 연결해서 직접 추가를 해주어야 합니다.


mysql에 root 아이디로 접속


$ mysql -uroot -p


kakiadmin 아이디 추가


mysql> CREATE USER 'kakiadmin'@'192.168.35.1' IDENTIFIED BY 'your_password';
mysql
> grant all privileges on *.* to 'kakiadmin'@'192.168.35.1';


여기서 주의할 사항은 아이디를 추가할 때 뒤에 붙이는 아이피는 집에서 테스트를 할 때는 게이트웨이 주소를 넣으셔야 하며, 차후 Azure에 서비스를 올리면 Azure 서비스에서 사용하는 아이피를 넣어야 한다는 것입니다. 


동일한 이름을 가지고 있어서 호출하는 Ip에 맞춰서 아이디가 각각 존재해야 한다는 점입니다.


어떤 아이피를 가지는 아이디를 추가해야하는지 확인하기 위해서 GetConnectTest 메소드를 호출하면 됩니다.



5. ValuesController.cs 수정


        /// <summary>
        ///     연결 테스트
        /// </summary>
        /// <returns></returns>
        [Route("~/getconnecttest")]
        public async Task<string> GetConnectTest()
        {
            return await MySQLHelper.Instance.GetConnectTestAsync();
        }


위의 코드를 추가한 후 실행하면 브라우저에서는 HTTP Error 403.14 - Forbidden 이런 오류가 출력되는데 무시하고, localhost:12267을 복사한 후 postman을 이용해서 테스트를 해봅니다.


정상적으로 연결이 완료되고, 데이터를 조회했다면 다음과 같은 결과가 반환됩니다.

실패했을 경우 1045 오류가 발생하면서, 메시지가 나오는데 그 곳에 나오는 아이피를 이용해서 아이디를 추가해주시면 됩니다.


여기서 제가 실패하지 않는 이유는 이미 로컬 아이피를 이용해서 계정을 등록해 놓았기 때문입니다. 처음하시는 분들은 여기서 반드시 오류가 발생합니다. 9번 항목을 참고해서 처리하세요



6. 데이터 조회


        // GET api/values
        [SwaggerOperation("GetAll")]
        public async Task<DataSet> Get()
        {
            return await MySQLHelper.Instance.ExecuteAsync("SELECT * FROM Products");
        }


ValuesController.cs에 Get메소드를 위와 같이 수정하고 실행 합니다.


postman에서 http://localhost:12267/api/values를 호출하면 결과가 반환 됩니다.


DataSet을 Json형태로 변환해서 반환합니다.

물론 여기서는 쿼리를 미리 입력해서 처리했지만, request로 받아서 처리할수도 있겠죠? 쿼리를 직접 받기 위해서는 Post를 이용해야 합니다.



7. 쿼리 직접 입력 받아서 실행하기


        // POST api/values
        [SwaggerOperation("Create")]
        [SwaggerResponse(HttpStatusCode.Created)]
        public async void Post([FromBody]JObject value)
        {
            var query = value["value"].ToString();
            var result = await MySQLHelper.Instance.ExecuteNonQueryAsync(query);
        }


ValueController.cs를 위와 같이 수정합니다. 특히 string을 JObject로 수정하는 것을 잊으면 않됩니다.


수정 완료 후 postman에서 아래와 같이 호출합니다.


post를 선택하고, http://localhost:12267/api/values를 입력합니다.

headers에 content-type은 x-www-form-urlencoded로 지정되어 있어야 하며, Body에 x-www-form-urlencoded를 선택한 후 value, INSERT Employees(LastName, FirstName) VALUES('Park','MunChan'); 를 각각 입력 후 send를 누릅니다.


물론 반환되는 결과는 없습니다. 호출후 db를 조회해보면 Employees 테이블에 레코드가 하나 추가되어있는 것을 볼 수 있습니다.



8. 서비스를 Azure에 올리기


프로젝트에서 마우스 오른쪽 -> Publish를 선택하면 아래와 같은 화면이 나옵니다. Create New에서 Publish를 눌러줍니다.



Create App Service에서 App 이름을 지정하고, 서브스크립션을 선택합니다. 비주얼 스튜디오 로그인된 계정으로 가입된 Azure 계정을 찾는것이 기본이며, 다른 계정을 선택하실 수도 있습니다. 


MVP는 MSDN 서브스크립션을 가지고 있기 때문에 선택했습니다. 그외 Resource Group, Hosting Plan등은 사용자가 임으로 만들어서 사용하시면 됩니다.


Create 버튼을 눌러서 서비스를 생성하면 됩니다.



배포가 완료되면 



완료화면이 출력됩니다. 이제 서비스를 호출해서 테스트 합니다.



9. 마무리 작업


postman으로 http://mysqlsample2018.azurewebsites.net/getconnecttest 주소를 get 호출하면


"Authentication to host 'xx.xx.xx.xx' for user 'kakiadmin' using method 'mysql_native_password' failed with message: Access denied for user 'kakiadmin'@'52.231.xx.xxx' (using password: YES)"


위와 같은 오류가 발생합니다. 즉, kakiadmin으로 MySQL 에 로그인을 하지 못했다는 내용입니다. 그런데 찾는 계정이 'kakiadmin'@'52.231.xx.xxx' 입니다. 이런 경우 해당 계정을 MySQL 에 추가해 주어야만 로그인이 가능합니다.


추가!!

뒤에 ip부분을 생략하고 싶으면 kakiadmin@% 이렇게 만들면 어떤 아이피든지 통과가 가능합니다. ㅜㅜ



4번에 있는 방법을 이용해서 계정을 추가해 주면 완료 가능합니다.


아이디를 추가한 후 다시 호출해보니 success connecting이라는 메시지가 출력되었습니다.


마지막으로 조회를 테스트해보니 정상적으로 조회되는 것을 알 수 있습니다.

최종 소스에서 조회하는 테이블을 Employees로 변경했습니다.


http://mysqlsample2018.azurewebsites.net/api/values


{
    "Table": [
        {
            "ProductID": 1,
            "ProductName": "Chai",
            "SupplierID": 1,
            "CategoryID": 1,
            "QuantityPerUnit": "10 boxes x 20 bags",
            "UnitPrice": 18,
            "UnitsInStock": 39,
            "UnitsOnOrder": 0,
            "ReorderLevel": 10,
            "Discontinued": 0
        },
        {
            "ProductID": 2,
            "ProductName": "Chang",
            "SupplierID": 1,
            "CategoryID": 1,
            "QuantityPerUnit": "24 - 12 oz bottles",
            "UnitPrice": 19,
            "UnitsInStock": 17,
            "UnitsOnOrder": 40,
            "ReorderLevel": 25,
            "Discontinued": 0
        },

.....



10. Azure debug


에저에 올라간 서비스의 에러를 확인하기 위해서는 참고에 있는 내용을 참고하시면 될 것 같습니다.



11. 소스

https://github.com/kaki104/ConnectingAzureToMySQL


Posted by MVP kaki104

집에 NAS가 하나 있습니다. 모두 아시는 내용이죠?? 이 녀석 사용하지 좀 오래 된 듯한데.. 하여간 지금 시놀로지(헤놀로지) 5.2-5644 Update 5 버전이 설치가 되어있고, 영화, 음악, 사진 등을 잘 넣어서 보고있는데.. 



오늘 아침에 갑지가 이 녀석에게 DB 설치가 가능할 것 같다는 느낌이 딱! ... 결국 3시간 만에 성공을 한 것 같습니다.



0. 참고


https://m.blog.naver.com/PostView.nhn?blogId=oioi9i&logNo=220408830865&proxyReferer=https%3A%2F%2Fwww.google.co.kr%2F

http://zetawiki.com/wiki/MySQL_원격_접속_허용



1. 설치는 쉬웠다!



피키지 센터에 MariaDB가 있어서 찾아보니 과거 MySQL의 친구(?) 버전이라 호화성이나 성능이 괜찮을 것 같아서 설치하기로 하고, Install 버튼을 누르니 특별히 뭐 물어보는 것도 없이 바로 설치 완료..


으흠;;



2. Admin계정 비밀번호 등은??



물개를 클릭하면 간단한 윈도우가 하나 뜨는데..



Change MariaDB password 버튼을 클릭해서, root 계정의 비밀번호를 변경할 수 있습니다. root가 admin인 것 같습니다..아마도..



3. DB설치는 되었는데, 어떻게 접속을 해야하지??


마리아 DB TX Tools 폴더에 오면 Tools 다운로드가 지원됩니다. 

https://mariadb.com/downloads/mariadb-tx/tools 


SQLyog를 선택하고 Download 버튼을 클릭하면, 응용프로그램이 다운 됩니다. 14일간 트라이얼로 사용할 수 있는 버전으로 정식으로는 99불을 주고 사면 됩니다.


물론 MySQL과 동일하기 때문에 MySQL 관리가 가능한 다른 프로그램을 사용하셔도 상관 없습니다.



4. DB 접속 1차 시도


처음 시도는 역시 실패! 실패 이유는 DB가 외부접속을 허용하지 않기 때문입니다.


DB를 외부접속 가능하게 하려면.. 3개의 DB 커맨드를 날려야 하는데..


이 커맨드를 날릴려면 NAS에 Telnet으로 접속을 해야 합니다.



5. NAS에 Telnet으로 접속하기



NAS에 Telnet으로 접속하기 위해서는 Control Panel에서 Terminal & SNMP를 선택해서 Enable Telnet service, Enable SSH service 2개를 체크 해 주고, Apply를 클릭합니다.


이제 Telnet을 이용해서 NAS에 접속 할 수 있습니다. 음음..그런데 윈도에 기본적으로 Telnet 클라이언트 설치가 되어 있지 않으니 설치를 해야겠죠?


Telnet 클라이언트 설치하는 부분에 대해서는 설명을 생략하겠습니다.



6. Telnet 클라이언트로 NAS 접속, MariaDB 접속


윈도우에서 Windows PowerShell을 실행 하고, 다음의 명령을 입력해서 NAS에 연결합니다. 아이피 주소는 자신의 NAS IP 주소입니다.


telnet 192.168.35.94



아이디와 패스워드를 입력해야 하는데..원래 사용하던 계정으로 로그인이 앙되더라구요..그래서 admin 계정을 사용할 수 있게 변경해야합니다.(원래 사용하던 계정을 사용해서 로그인할 수 있으면 그걸로 그냥 하셔도 될 듯..)



User -> admin -> Disallow the user to change account password, Disable this account 2가지의 체크를 풀고, OK를 입력합니다. 


BusyBox v1.16.1 (2015-11-12 18:06:25 CST) built-in shell (ash)
Enter 'help' for a list of built-in commands.


kaki_server>


로그인이 완료되면 대충 이런 메시지가 출력됩니다. 후후


이제 MariaDB에 연결해서 쿼리를 날려줘야 하는데. db에 연결하는 명령어를 몰라서 찾아보니..


kaki_server> mysql -u root -p

Enter password:
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 4
Server version: 5.5.47-MariaDB Source distribution


Copyright (c) 2000, 2015, Oracle, MariaDB Corporation Ab and others.


Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.


MariaDB [(none)]>


password는 아까 변경해준 녀석을 입력하시면 될 것 같습니다. 아마도..



7. 외부 접속 가능하도록 쿼리 입력


MariaDB [(none)]> INSERT INTO mysql.user (host,user,authentication_string,ssl_cipher, x509_issuer, x509_subject) VALUES ('%','root',password('패스워드'),'','','');
Query OK, 1 row affected (0.00 sec)


MariaDB [(none)]> GRANT ALL PRIVILEGES ON *.* TO 'root'@'%';
Query OK, 0 rows affected (0.00 sec)


MariaDB [(none)]> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.00 sec)


굵은 글씨 부분을 차례대로 입력하시고, 패스워드는 패스워드를 입력해 주세요, 


여기까지 하시면 외부에서 db에 연결 할 수 있습니다.



8. DB 접속 2차 시도..


4번으로 돌아가서 비밀번호를 입력하고 connect를 입력하면~



짜잔~ 연결이 되었습니다.



9. 주의사항


telnet 접속이 가능하도록 열어 놓고 작업을 한 후 바로 닫아 주어야 합니다. 그렇지 않으면 무지막지한 공격이 들어옵니다..쿨럭..


Posted by MVP kaki104
TAG mariaDB, mysql, Nas

이번 포스트는 UWP 앱 숙원 2탄으로 멀티 인스턴스에 대한 내용을 다루어 보도록 하겠습니다.


ps. 어제 우리나라는 지고, 오늘 일본은 이겼군요.....ㅡ,.ㅡ



1. 참고

https://docs.microsoft.com/ko-kr/windows/uwp/launch-resume/multi-instance-uwp



2. 멀티 인스턴스 앱을 만들기 위한 준비


멀티 인스턴스 앱을 만들기 위해서는 Multi-Instance App Project Templates.VSIX를 설치하고, 그 템플릿을 이용해서 앱을 만들어 주어야 합니다.


다운로드


물론 기존 앱에도 package.appxmanifest 파일을 수동으로 수정해서 만들 수 있을 것으로 생각됩니다.




3. 프로젝트 생성


프로젝트를 생성하는 화면에 2개의 템플릿이 추가됩니다. 


1) Multi-Instance Redirection UWP app

앱의 시작점에서 인스턴스 생성하거나, 기존 인스턴스 중에 하나를 선택해서 활성화 시키는 앱을 만듭니다.


2) Multi-Instance UWP app

일반적인 멀티 인스턴스 UWP 앱을 생성합니다.



4.  Package.appxmanifest


         xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4"
xmlns:iot2="http://schemas.microsoft.com/appx/manifest/iot/windows10/2" 

...

<Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="KakiViewer.App" desktop4:SupportsMultipleInstances="true" iot2:SupportsMultipleInstances="true">

...



package.appxmanifest 파일에서 굵은 글씨 부분이 새로 추가된 기능입니다. 또한, 위위의 내용처럼 이 멀티 인스턴스 앱은 데스크탑과 IoT에서만 가능하다는 것을 알 수 있습니다.



5. KakiViewer 프로젝트의 키 포인트!


이미지 뷰어를 만들어 보았습니다.


1) 프로젝트 버전 설정


Target version, Min version 모두 Windows 10, version 1803로 선택하셔야 합니다.


2) 확장자 연결


Package.appxmanifest의 Declarations 탭에서 File Type Associations를 추가해서 png 파일과 연결을 했습니다.


3) 파일 열기 프로그램으로 실행된 경우 처리


App.xaml.cs


        protected override void OnFileActivated(FileActivatedEventArgs args)
        {
            if (!(Window.Current.Content is Frame rootFrame))
            {
                rootFrame = new Frame();
                Window.Current.Content = rootFrame;
            }
            if (rootFrame.Content == null)
            {
                var parameter = string.Empty;
                if (args.Files.Any())
                    parameter = args.Files[0].Path;
                rootFrame.Navigate(typeof(MainPage), parameter);
            }
            Window.Current.Activate();
        }


파일 열기로 앱이 시작되면 OnFileActivated를 구현해야 합니다. 그리고, 전달 받은 파일의 정보는 오직 Path만 알 수 있내요... 그 이상의 정보를 알아내는 방법은 현재 상황에서는 모르겠습니다. 디버그를 통해서 직접 확인하세용


4) 이미지 파일을 열어서 표시하는 방법


            var firstFile = await StorageFile.GetFileFromPathAsync(filePath);
            if (firstFile == null) return;

            using (var stream = await firstFile.OpenReadAsync())
            {
                var bi = new BitmapImage();
                bi.SetSource(stream);
                Image.Source = bi;
            }


에..여기서 firstFile이 StorageFile형태이기는 한데..파일의 정보를 알 수는 없습니다.

아마 보안 사항이기 때문일지도..ㅡㅡ;;; 머징..


아니면, filePath 중 폴더 정보만 빼내서 폴더 정보를 먼저 가지고 오고 그 폴더에서 파일을 찾아서 가져오면 상세 정보도 알 수 있을 듯 합니다. 물론 이 경우 지난 포스트에서 다루었던 broadFileSystemAccess을 사용해야 합니다.


5) 디버그 하는 방법


프로젝트 속성 -> Debug -> Start action -> Do not launch, but debug my code when it starts를 체크하고 디버그로 실행 합니다.


파일 탐색기에서 png 파일을 마우스 오른쪽 버튼으로 눌러서 열기 할 프로그램을 선택하시면 디버그가 가능 합니다.



6. 소스

https://github.com/kaki104/UWP_17134


Posted by MVP kaki104


오랜만에 포스트를 작성하네요. 음..변명을 하자면.. 챗봇 공부를 하고 있는 중이였는데 Build 2018 동영상이 나와서 그것 좀 보다가..끝나고 나니..뭘해야할지..막막


사실 Windows ML을 하려고 했으나, 머신 러닝을 위해서는 파이선을 공부해야 한다고 해서 한 몇일 고민하다가.. 일단 파이선 공부는 뒤로 미루고, Build에서 발표된 새로운 UWP에 기능에 대해서 집중하기로 마음을 먹고.. 마음의 위안을 삼은체 탱자 탱자 하다가.. 오늘에서야 ...ㅋㅋㅋ



하지만, 오늘 대박 소식을 전하려고 합니다. 그동안 UWP의 숙원 사업(?) 중에 하나가 사용자의 허가를 받지 않은 로컬 폴더와 파일의 목록을 조회하고, 열어서 가지고 오는 것이 있었습니다.


이 부분이 않되어서 참 많은 어려움이 있었죠..하지만, 이번 Build에서 드디어 추가되었습니다~ 와~~~ 그러면 자세히 알아 보도록 하겠습니다.



1. 참고

https://docs.microsoft.com/en-us/windows/uwp/files/file-access-permissions

https://github.com/Microsoft/AppModelSamples



2. BroadFileSystemAccess


이 녀석이 그 녀석입니다. 사용자의 허가(앱을 설치할 때)를 받으면 그 이후 부터는 폴더와 파일에 접근할 때 별도의 허가 없이도 가능합니다. 하지만, 이 녀석을 사용하기 위해서는 선행 조건이 있습니다.


UWP 앱 Target, Min 버전이 17134이여야 합니다.

또한, Windows 10 버전도 1803 버전이여야 합니다.

* 물론 Visual Studio 최신 버전이 필요하고, Windows SDK도 최신 버전이 필요합니다.

이 두가지 조건에 만족한다면, 바로 사용이 가능하며, 추가로 약간 귀찮은 작업을 해줘야 합니다.


Capability의 기능이기 때문에 Package.appxmanifest에 정의를 해줘야 하는데...

GUI 화면에서는 이 녀석을 찾을 수가 없습니다. (현재는.. 추후 추가될 것이라고 생각됩니다만..) 그래서, Package.appxmanifest 파일을 xaml editer로 열어서 수동 편집해 주어야 합니다.


<Package
  xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
  xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
  xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
  xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5"
  xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
  IgnorableNamespaces="uap mp uap5 rescap">


...


  <Capabilities>
    <Capability Name="internetClient" />
    <rescap:Capability Name="broadFileSystemAccess" />
  </Capabilities>




3. C:/ 폴더와 서브 폴더 목록을 가지고와 볼까요?


var rootFolder = await StorageFolder.GetFolderFromPathAsync(@"c:\");


이렇게 코딩하면 바로 c:/ 루트 폴더를 가지고 올 수 있습니다. 아무것도 묻거나 따지지 않습니다~ 여기가 바로 신세계입니다~ 


서브 폴더는 어떻게 가지고 오냐구요?

그건 이전과 동일하게, GetFoldersAsync()를 이용하면 됩니다.


        private async void GetSubDirectories(StorageFolder folder, TreeViewNode folderNode, int depth = 0)
        {
            try
            {
                if (depth > 1) return;
                var subDirs = await folder.GetFoldersAsync();
                if (subDirs.Any() == false || folderNode.Children.Any()) return;
                foreach (var subDir in subDirs)
                {
                    var subDirNode = new TreeViewNode
                    {
                        Content = new DirectoryModel
                        {
                            Name = subDir.Name,
                            Path = subDir.Path,
                            HasSubDirectory = false
                        },
                    };
                    GetSubDirectories(subDir, subDirNode,depth + 1);
                    folderNode.Children.Add(subDirNode);
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }
        }



4. WPF 프로그램을 UWP로 대체하는 날이 얼마 남지 않은 듯 합니다.


다만, 걱정은 우리나라 WPF 개발 시장과 개발자의 인식이 좀.. 거시기 하다는.. 흑흑..

왜 아직도 윈폼으로 개발하고 있나요..엉엉(물론 지금 제가 진행하는 프로젝트도 WPF 개발자를 구하지 못해서 방황하는 중이지만..) 그래도 이제는 WPF 더 나아가서 UWP 프로젝트가 마니 생기기를..


당분간 새로 추가된 기능들을 아래 소스 폴더에 정리할 예정입니다.



5. 소스


https://github.com/kaki104/UWP_17134



PS. 27일날 갑자기 방문자수가 500을 넘게 찍었던데 무슨일이 있었떤 거죠??



6. 질문 주신 내용에 대한 소스 추가 합니다.


DownloadsFolder.CreateFileAsync
DownloadsFolder.CreateFolderAsync

를 사용하면 파일 생성은 가능하지만 이미 생성된 파일이나 폴더가 있을 경우 get 할 수 있는 OpenIfExists 같은 CollisionOption 을 사용할 수가 없네요. BroadFileSystemAccess 을 사용해도 마찬가지 인것 같고요

picker 를 이용하지 않고 UWP 사용자 폴더의 파일이나 폴더에 접근하여 가져오고 데이터를 쓰고 하는 다른 방법이 있을까요?


6-1. Create


        private async void CreateFileInSelectedFolder()
        {
            if (_selectedFolder == null) return;
            var file = await _selectedFolder.CreateFileAsync("TextFile.tmp", CreationCollisionOption.OpenIfExists);
            if (file == null) return;
            var msg = new MessageDialog("Successful creation or opening operation");
            await msg.ShowAsync();
        }



6-2. Open


        private async void OpenFile()
        {
            if (_selectedFolder == null) return;
            try
            {
                var file = await _selectedFolder.GetFileAsync("TextFile.tmp");
                if (file == null) return;
                var msg = new MessageDialog("Successful opening operation");
                await msg.ShowAsync();
            }
            catch (FileNotFoundException ffe)
            {
                var msg = new MessageDialog(ffe.Message);
                await msg.ShowAsync();
            }
            catch (Exception)
            {
                throw;
            }
        }


문의하신 내용에 대한 답변이 되었는지 모르겠습니다. 추가로 궁금하신 사항은 다시 적어주세요 감사합니다.


소스도 올려 놓았으니 참고하세용


Posted by MVP kaki104


이번 포스트는 마이크로소프트 IoT Device의 걸작! AI 스피커의 기대주인 하만/카돈 인보크 AI 스피커 설치 및 실행기 입니다.

이렇게 쓰고, 삽질기라고 부르기는 하지만;;;


어제 설치하고 사용한다고 이제서야 동영상 편집하고 올립니다. PPT는 없습니다. 그냥 MS의 AI 스피커가 이런거구나 하고 재미로 보시면 될 것 같습니다. 참 채널에 구독 신청하시면 더 빨리 올라가는 동영상을 확인 하실 수 있습니다~


유튜브 채널 바로가기


즐감 하세용~



Posted by MVP kaki104


이번 포스트는 얼마전 대한민국 정부에서 AI 서비스 개발을 지원하기 위해 오픈한 AI 오픈 이노베이션 허브에서 제공하는 한글 음성 인식 기능을 이용해서 한글 인식을 하는 방법에 대한 내용입니다.


개발 가이드를 보면 Java, PHP, C++, Python Node.js를 이용한 예제가 존재 합니다.  그런데, C# 예제가 존재 하지 않아 직접 삽질을 했습니다.


이 API는 상업용 목적으로 사용하기 위해서는 기술 이전을 받아야 하고, 일반 업무용 목적의 사용이라면, 1일기준 제한된 범위에서 특별한 제약이 없이 사용이 가능하다고 합니다.


우선 이런 좋은 서비스를 Open API 형태로 제공해 준 여러 관계자 분들께 감사드리고 싶습니다. 이 API를 이용하면 좀더 쉽게 한글 음성 인식을 지원하는 앱 개발이 가능할 것 같습니다. 


동영상을 보시기 전에 채널 구독 먼저 부탁드립니다~ 유튜브 채널 바로가기



PPT


aihub.pptx


Posted by MVP kaki104



Windows 10 IoT Core에 음성으로 명령을 내려서 음악을 재생, 다음곡, 이전곡, 장르 검색의 기능을 수행하는 UWP 앱을 개발하는 과정을 동영상으로 만들었습니다. 개발 소스, 동영상, ppt를 참고하시면 여러분도 멋진 앱을 만드실 수 있을 것이라고 생각합니다.


감상하시기 전에 유튜브 채널 구독 신청 부탁드립니다~. 유튜브 채널 바로가기



완성된 앱 시연 영상




Part 1


* 환경 및 준비
* 참고 자료
* Media Player
* SRGS
* IoT Player with voice commands
* Begin Playback
* PC 환경 설정 확인
* 앱 개발 순서
* 뮤직 플레이 페이지 구현 로직




Part 2


* 프로젝트 추가
* package.appmanifest 설정
* 앱 초기화 모듈 구현
- 외장 디스크 연결 확인
- 일단 음악 재생이 되도록 작업
* MediaPlayerViewModel.cs
- Navigation_Navigated 구현
- 음성 인식 초기화
- MediaPlayerElement 초기화




Part 3


* SRGS 만들기
- begin playback
* MediaPlayerViewModel.cs
- SRGS 파일 불러와서 음성 인식 컴파일
* 시작 페이지를 MediaPlayerPage로 변경
* MediaPlayerViewModel.cs
- 시작하면서 음성 인식 시작 하도록
- 음성 인식 후 넘어오는 데이터 확인
- tag에 ECMAScript를 이용해서 반환 결과 변경
- 음성 인식 결과 확인
- 음성 인식 결과를 action에 넣어서 어떤 명령인지 구분해서 명령 실행
- BeginPlaybackAsync() 실행
- DispatcherHelper 초기화, 사용
- 결과 확인




Part 4


* 음성 입력 대기 상태 구현 및 음성 명령 종류에 따른 분기 처리 시작
* SRGS에 기능 추가
- pause playback
- MediaPlayerViewModel에도 pause playback 구현
- stop playback
- next playback
- previous playback
* MediaPlayerPage.xaml 디자인 작업
- Binding




Part 5


* Begin playback 구현
* MediaPlayerViewModel.cs
- InitPlaylistAsync()
- 외장 드라이브에서 파일 목록 가지고오도록...
- BeginPlaybackAsync() 수정
- GetMusicPropertiesAsync()를 사용해서 타이틀, 앨범, 아티스트, 이미지 출력
- PosterSource 수정
* MediaPlayerBehavior 추가 및 구현
- CurrentPlaybackState 추가
* MediaPlayerBehavior를 MediaPlayerElement와 연결 - blend
- Binding
* MediaPlayerViewModel.cs
- Init() 수정 - PropertyChanged 추가
* MediaPlayerBehavior 수정
- MediaPlayer를 MediaPlayerElement에 추가




Part 6


* 외장 드라이브에서 파일 목록 가지고 오는 것 포기!! 그래서 Music Library에서 파일 목록을 가지고 오는 방법으로 변경
- SD 카드에 Music Library에 파일 복사하기..
* MediaPlayerViewModel.cs
- CreateFileQueryWithOptions()를 이용해서 파일 필터링과 목록 조회를 동시에
* Pause playback 구현
* CommandMediaPlayer enum 추가
- Messenger를 이용해서 MediaPlayerBehavior에 명령 전달
* MediaPlayerBehavior 수정
- Messenger 구독
- Source 프로퍼티 추가
- ExecuteCommandMediaPlayer 구현




Part 7


** Next, Previous 기능 구현 동영상은 생략 합니다.

(편집했는데..날려서..ㅜㅜ 혹~~~~~~시라도..필요하다고 하시는 분이 계시면..한 10분 정도;; 그러면 다시 편집해서 추가하도록 하겠습니다~)


* 음악 검색 기능 구현
- Filter 추가
* MediaPlayerViewModel.cs
- SelectSourceAsync()
* SRGS.xml 파일에 장르 추가
- 재생 완료 후 다음 곡으로 넘어가는 기능 구현
- ContinuousRecognitionSession_Completed 이벤트 발생 이유에 대해..
* 존재하지 않는 장르를 이야기하면 미친듯이 돌아가는 버그!! 잡아주세요




PPT


iot-mediaplayer.pptx








Posted by MVP kaki104

티스토리 툴바